package com.cmc.cloud.cmclink.doc.service.impl.manifest;

import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.fastjson.JSON;
import com.baomidou.lock.annotation.Lock4j;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.cmc.cloud.cmclink.doc.api.document.emums.*;
import com.cmc.cloud.cmclink.doc.api.document.rep.manifestrep.manifest.*;
import com.cmc.cloud.cmclink.doc.api.document.rep.manifestrep.manifestcargo.ManifestCargoRespVO;
import com.cmc.cloud.cmclink.doc.api.document.rep.manifestrep.manifestcargo.ManifestCargoUpdateReqVO;
import com.cmc.cloud.cmclink.doc.api.document.rep.manifestrep.manifestcargocntr.ManifestCargoCntrUpdateReqVO;
import com.cmc.cloud.cmclink.doc.api.document.rep.manifestrep.manifestcntr.ManifestCntrEdiBaseVO;
import com.cmc.cloud.cmclink.doc.api.document.rep.manifestrep.manifestcntr.ManifestCntrRespVO;
import com.cmc.cloud.cmclink.doc.api.document.rep.manifestrep.manifestcntr.ManifestCntrUpdateReqVO;
import com.cmc.cloud.cmclink.doc.api.document.rep.manifestrep.manifestscn.ManifestScnUpdateReqVO;
import com.cmc.cloud.cmclink.doc.api.document.req.so.SoForecastBaseVO;
import com.cmc.cloud.cmclink.doc.constants.errorconstant.ManifestErrorCodeConstants;
import com.cmc.cloud.cmclink.doc.constants.errorconstant.SpecialCargoFlexiErrorCodeConstants;
import com.cmc.cloud.cmclink.doc.convert.*;
import com.cmc.cloud.cmclink.doc.entity.*;
import com.cmc.cloud.cmclink.doc.framework.listener.ManifestCntrExcelListener;
import com.cmc.cloud.cmclink.doc.mapper.*;
import com.cmc.cloud.cmclink.doc.rpcservice.CntrTransferService;
import com.cmc.cloud.cmclink.doc.service.impl.so.SoCargoServiceImpl;
import com.cmc.cloud.cmclink.doc.service.impl.so.SoScnServiceImpl;
import com.cmc.cloud.cmclink.doc.service.impl.so.SoServiceImpl;
import com.cmc.cloud.cmclink.doc.service.impl.so.SoVslvoyServiceImpl;
import com.cmc.cloud.cmclink.doc.service.manifest.*;
import com.cmc.cloud.cmclink.doc.util.CheckUtil;
import com.cmc.cloud.cmclink.doc.util.I18nsUtil;
import com.cmc.cloud.cmclink.doc.util.so.CompareUtils;
import com.cmc.cloud.cmclink.doc.vo.manifestvo.manifest.ManifestCancelVO;
import com.cmc.cloud.cmclink.doc.vo.manifestvo.manifest.ManifestSalveReqVO;
import com.cmc.cloud.cmclink.doc.vo.manifestvo.manifest.ManifestTransitTermReqVO;
import com.cmc.cloud.cmclink.doc.vo.manifestvo.manifestcargo.ManifestCargoSumVO;
import com.cmc.cloud.cmclink.doc.vo.manifestvo.manifestcntr.*;
import com.cmc.cloud.cmclink.doc.vo.sovo.soforecastvo.SoForecastCntrSumVO;
import com.cmc.cloud.cmclink.eqm.api.model.response.CntrBaseInfoDTO;
import com.cmc.cloud.cmclink.eqm.api.resource.CntrInfoApi;
import com.cmes.framework.common.exception.ErrorCode;
import com.cmes.framework.common.exception.util.ServiceExceptionUtil;
import com.cmes.framework.common.pojo.CommonResult;
import com.cmes.framework.common.pojo.PageResult;
import com.cmes.framework.ex.Assertion;
import com.cmes.framework.excel.core.util.ExcelUtils;
import com.cmes.framework.mybatis.core.dataobject.BaseDO;
import com.cmes.framework.mybatis.core.query.LambdaQueryWrapperX;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.math.BigDecimal;
import java.net.URLEncoder;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;


/**
 * 舱单主 Service 实现类
 *
 * @author chenzheng
 */
@Service
@Validated
@Slf4j
public class ManifestServiceImpl implements ManifestService {
    @Resource
    private SoServiceImpl soService;
    @Resource
    private SoCargoServiceImpl soCargoService;
    @Resource
    private SoScnServiceImpl soScnService;
    @Resource
    private SoVslvoyServiceImpl soVslvoyService;
    @Resource
    private ManifestMapper manifestMapper;
    @Resource
    private ManifestCargoMapper manifestCargoMapper;
    @Resource
    private ManifestScnMapper manifestScnMapper;
    @Resource
    private ManifestCntrMapper manifestCntrMapper;
    @Resource
    private ManifestCargoCntrMapper manifestCargoCntrMapper;

    @Resource
    private ManifestVslvoyMapper manifestVslvoyMapper;
    @Resource
    private CntrTransferService cntrTransferService;
    @Resource
    private ManifestCargoService manifestCargoService;
    @Resource
    private ManifestCntrService manifestCntrService;
    @Resource
    private ManifestScnService manifestScnService;
    @Resource
    private ManifestVslvoyService manifestVslvoyService;
    @Resource
    CntrInfoApi cntrInfoApi;
    @Resource
    SoForecastMapper soForecastMapper;
    @Resource
    private PortDescMapper portDescMapper;
    @Resource
    private BlnoMapper blnoMapper;
    @Resource
    private BlnoScnMapper blnoScnMapper;

    @Resource
    private BlnoCargoMapper blnoCargoMapper;

    @Resource
    private I18nsUtil i18nsUtil;

    private static final int SCN_DEFAULT_SIZE = 4;

    private static final DateTimeFormatter FORMATTER_AMEND = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");

    @Override
    @Transactional(rollbackFor = Exception.class)
    @Lock4j(name = "manifestUnique", keys = {"'batchCreateMasterFromSo'", "#userId"})
    public List<Long> batchCreateMasterFromSo(List<Long> soIdList, String userId, String dataSource, boolean multiFlag) {
        //如果已经有 SO 信息，并且订舱已经接受。在这个界面查出没有生成过 Manifest 的订舱记录，勾选批量生成 Manifest 信息。这个时候生成的 Manifest 信息并不全（未确认状态），需要到 Maintain Manifest 界面继续补全Manifest信息，检查数据是否完整，若完整选择保存并确认舱单
        String errorMsg = "只能操作订舱已确认的数据";
        List<SoDO> solist = soService.checkSoStatusByUserId(soIdList, Arrays.asList(SoStatusEnum.BookingConfirmed.getValue()), userId, errorMsg);
        List<String> bookingNos = solist.stream().map(e -> e.getBookingNo()).collect(Collectors.toList());
        List<Long> soIds = solist.stream().map(e -> e.getId()).collect(Collectors.toList());
        //多联的需要校验por不等于pol
        if (multiFlag) {
            Optional<SoDO> soDoOptional = solist.stream().filter(item -> StringUtils.equals(item.getPolCode(), item.getPorCode())).findAny();
            if (soDoOptional.isPresent()) {
                Assertion.assertNotEmpty(soDoOptional.get().getBookingNo()).raise0(ManifestErrorCodeConstants.MANIFEST_CHECK_MULTI_ERROR.getCode(), soDoOptional.get().getBookingNo());
            }
        }
        //只查询主舱单
        List<ManifestDO> manifestList = findMasterByBookingNos(bookingNos);
        if (CollectionUtil.isNotEmpty(manifestList)) {
            Set<String> existBookingNos = manifestList.stream().map(e -> e.getBookingNo()).collect(Collectors.toSet());
            throw ServiceExceptionUtil.exception(new ErrorCode(ManifestErrorCodeConstants.MANIFEST_EXISTS.getCode(), StringUtils.join(existBookingNos, ",") + ManifestErrorCodeConstants.MANIFEST_EXISTS.getMsg()));
        }
        Map<Long, SoCargoDO> soCargoMap = soCargoService.selectBySoIds(soIds);
        Map<Long, SoScnDO> soScnMap = soScnService.selectBySoIds(soIds);
        Map<Long, List<SoVslvoyDO>> soVslvoyMap = soVslvoyService.seleteBySoIdsMap(soIds);
        if (soCargoMap.size() != soIdList.size() || soScnMap.size() != soIdList.size()) {
            throw ServiceExceptionUtil.exception(ManifestErrorCodeConstants.MANIFEST_ERROR_NUM);
        }
        List<Long> ids = new ArrayList<>(16);
        solist.forEach(so -> {
            ManifestDO manifest = ManifestConvert.INSTANCE.convert(so);
            manifest.setId(null);
            manifest.setManifestNo(so.getBlNo());
            manifest.setConfirmStatus(ConfirmStatusEnum.N.getValue());
            manifest.setDataSource(dataSource);
            reSetNull(manifest);
            manifestMapper.insert(manifest);
            ids.add(manifest.getId());

            ManifestCargoDO manifestCargo = ManifestCargoConvert.INSTANCE.convert(soCargoMap.get(so.getId()));
            manifestCargo.setId(null);
            manifestCargo.setManifestId(manifest.getId());
            reSetNull(manifestCargo);
            manifestCargoMapper.insert(manifestCargo);

            ManifestScnDO manifestScn = ManifestScnConvert.INSTANCE.convert(soScnMap.get(so.getId()));
            manifestScn.setId(null);
            manifestScn.setManifestId(manifest.getId());
            reSetNull(manifestScn);
            manifestScnMapper.insert(manifestScn);
            List<ManifestVslvoyDO> manifestVslvoyList = ManifestVslvoyConvert.INSTANCE.convertSoList(soVslvoyMap.get(so.getId()));
            if (CollectionUtil.isNotEmpty(manifestVslvoyList)) {
                manifestVslvoyList.forEach(e -> {
                    e.setId(null);
                    e.setManifestId(manifest.getId());
                    reSetNull(e);
                    if (e.getRouteNo() == 1 && TransmodeEnum.LINER.getValue().equals(e.getTransmode()) == false) {
                        manifest.setPreVessel(e.getVesselCode());
                        manifest.setPreVoyage(e.getVoyageEx());
                        manifestMapper.updateById(manifest);
                    }
                });
                manifestVslvoyMapper.insertBatch(manifestVslvoyList);
            }
        });
        return ids;
    }

    public Map<String, String> selectDescByPortsMap(Set<String> portSet) {
        // 查询港口描述
        if (CollectionUtil.isEmpty(portSet)) {
            return new HashMap<>(8);
        }
        LambdaQueryWrapper<PortDescDO> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.in(PortDescDO::getPortCode, portSet);
        queryWrapper.eq(PortDescDO::getEnable, true);
        queryWrapper.orderByDesc(PortDescDO::getDefaultFlag);
        queryWrapper.orderByAsc(PortDescDO::getSortNo);
        return portDescMapper.selectList(queryWrapper).stream().collect(Collectors.toMap(PortDescDO::getPortCode, PortDescDO::getPortDesc, (oldValue, newValue) -> oldValue));
    }

    public void reSetNull(BaseDO baseInfo) {
        baseInfo.setDeleted(null);
        baseInfo.setCreator(null);
        baseInfo.setCreateTime(null);
        baseInfo.setUpdater(null);
        baseInfo.setUpdateTime(null);
    }

    public static final String CONFIRMED_STATUS = "Y";

    @Override
    @Transactional(rollbackFor = Exception.class)
    @Lock4j(name = "manifestUnique", keys = {"'importCntr'", "#userId"})
    public void importCntr(MultipartFile file, String userId, HttpServletResponse response) throws Exception {
        List<String> excelTypeList = Arrays.asList("XLS", "XLSX");
        ManifestCntrExcelListener excelListener = new ManifestCntrExcelListener();
        String[] fileNameAndType = file.getOriginalFilename().split("\\.");
        String fileName = fileNameAndType[0];
        String fileType = fileNameAndType[1];
        if (excelTypeList.contains(fileType.toUpperCase()) == false) {
            response.setContentType("application/json;charset=UTF-8");
            response.getOutputStream().write(JSON.toJSONString(CommonResult.success("文件格式错误")).getBytes());
            return;
        }
        List<ManifestCntrImportVo> importVos = EasyExcel.read(file.getInputStream(), ManifestCntrImportVo.class, excelListener).autoCloseStream(true).doReadAllSync();
        if (CollectionUtil.isEmpty(importVos)) {
            response.setContentType("application/json;charset=UTF-8");
            response.getOutputStream().write(JSON.toJSONString(CommonResult.success("无数据导入")).getBytes());
            return;
        }
        boolean importTypeCn = excelListener.getImportTypeCn();
        // 判断导入的数据类型
        List<String> sizeTypeList = new ArrayList<>();
        // 检查箱信息
        List<String> cntrNoList = new ArrayList<>();
        // 检查是否已经存在
        List<String> manifestNos = new ArrayList<>();
        importVos.forEach(e -> {
            e.checkNullData(importTypeCn);
            sizeTypeList.add(e.getCntrSize() + e.getCntrType());
            if (StringUtils.isNotEmpty(e.getCntrNo())) {
                cntrNoList.add(e.getCntrNo());
            }
            if (StringUtils.isNotEmpty(e.getManifestNo())) {
                manifestNos.add(e.getManifestNo());
            }
        });
        Map<String, CntrBaseInfoDTO> cntrBaseInfoMap = findCntrInfo(cntrNoList);
        List<String> commonCntrTypeList = cntrTransferService.getCntrTypeStringListExist(sizeTypeList);
        List<ManifestDO> haveManifestList = findByManifestNosList(manifestNos, userId);
        Map<String, ManifestDO> havedManifestNoMap = haveManifestList.stream().collect(Collectors.toMap(ManifestDO::getManifestNo, e -> e));
        Map<String, ManifestCntrDO> havedManifestIdCntrMap = findByManifestIdAndCntrNoMap(haveManifestList.stream().map(e -> e.getId()).collect(Collectors.toList()));
        // 检查数据
        List<Long> cntrDeleteIds = new ArrayList<>();
        AtomicReference<Boolean> checkAll = new AtomicReference<>(true);
        ManifestCntrLclReqVO cntrLclReq = new ManifestCntrLclReqVO();
        cntrLclReq.setCntrNoList(cntrNoList);
        List<String> bookingVesselList = new ArrayList<>(16);
        List<String> bookingVoyageList = new ArrayList<>(16);
        cntrLclReq.setBookingVesselList(bookingVesselList);
        cntrLclReq.setBookingVoyageList(bookingVoyageList);
        List<ManifestCntrLclRespVO> cntrLclList = new ArrayList<>(16);
        List<String> errorCntrTip = new ArrayList<>(16);
        importVos.forEach(e -> {
            if (StringUtils.isNotEmpty(e.getCntrSize()) && StringUtils.isNotEmpty(e.getCntrType())) {
                String cntrSizeType = e.getCntrSize() + e.getCntrType();
                if (commonCntrTypeList.contains(cntrSizeType) == false) {
                    e.getErrorMsgList().add(cntrSizeType + "未匹配");
                }
            }
            if (StringUtils.isNotEmpty(e.getManifestNo())) {
                ManifestDO manifestInfo = havedManifestNoMap.get(e.getManifestNo());
                if (manifestInfo == null) {
                    e.getErrorMsgList().add(e.getManifestNo() + "未查询到");
                } else {
                    bookingVesselList.add(manifestInfo.getBookingVessel());
                    bookingVoyageList.add(manifestInfo.getBookingVoyage());
                    if (StringUtils.isNotEmpty(e.getCntrNo())) {
                        String key = manifestInfo.getId() + e.getCntrNo();
                        ManifestCntrDO manifestCntrInfo = havedManifestIdCntrMap.get(key);
                        if (CONFIRMED_STATUS.equals(manifestInfo.getConfirmStatus())) {
                            e.getErrorMsgList().add(e.getManifestNo() + "已确认，不能添加");
                        }
                        // 判断箱信息是否符合 SNL
                        CntrBaseInfoDTO cntrInfo = cntrBaseInfoMap.get(e.getCntrNo());
                        if (cntrInfo != null) {
                            if (Objects.equals(cntrInfo.getCntrSize(), e.getCntrSize()) == false) {
                                e.getErrorMsgList().add(e.getCntrSize() + "尺寸未匹配SNL");
                            } else if (Objects.equals(cntrInfo.getCntrType(), e.getCntrType()) == false) {
                                e.getErrorMsgList().add(e.getCntrType() + "箱型未匹配SNL");
                            } else {
                                //皮重添加
                                e.setCntrTare(cntrInfo.getTareWeight());
                            }
                            e.setCntrOwner(CntrOwnerEnum.SNL.getValue());
                        } else {
                            // 箱号规则要判断
                            if (CheckUtil.isValidCntrNo(e.getCntrNo()) == false) {
                                //errorCntrTip.add(e.getCntrNo());
                            }
                            e.setCntrOwner(CntrOwnerEnum.SOC.getValue());
                        }
                        // 绑定关系
                        if (manifestCntrInfo != null) {
                            //导入如果提单下面已经存在相同的箱号直接覆盖导入。
                            cntrDeleteIds.add(manifestCntrInfo.getId());
                        }
                        //新增的数据要判断
                        cntrLclList.add(new ManifestCntrLclRespVO(
                                e.getCntrNo(), e.getSealNo(), e.getCntrType(), e.getCntrSize(), e.getCntrOwner(),
                                manifestInfo.getManifestNo(), manifestInfo.getMasterMfNo(), manifestInfo.getPayment(),
                                null, manifestInfo.getBookingVessel(), manifestInfo.getBookingVoyage()));
                        e.setManifestId(manifestInfo.getId());
                    }
                }
            }
            if (CollectionUtil.isNotEmpty(e.getErrorMsgList())) {
                checkAll.set(false);
            }
        });
        if (checkAll.get()) {
            // 匹配是否拼箱
            cntrLclReq.setCntrDeleteIds(cntrDeleteIds);
            cntrLclList.addAll(manifestMapper.selectManifestLcl(cntrLclReq));
            Map<String, List<ManifestCntrLclRespVO>> cntrMap = cntrLclList.stream().collect(Collectors.groupingBy(e -> e.getBookingVessel() + "_" + e.getBookingVoyage() + "_" + e.getCntrNo()));
            importVos.forEach(e -> {
                ManifestDO manifest = havedManifestNoMap.get(e.getManifestNo());
                String keyLcl = manifest.getBookingVessel() + "_" + manifest.getBookingVoyage() + "_" + e.getCntrNo();
                List<ManifestCntrLclRespVO> matchCntrList = cntrMap.get(keyLcl);
                e.setLcl(checkLcl(matchCntrList, manifest));
            });
            if (CollectionUtil.isNotEmpty(cntrDeleteIds)) {
                manifestCntrMapper.deleteBatchIds(cntrDeleteIds);
            }
            // 保存数据
            manifestCntrMapper.insertBatch(ManifestCntrConvert.INSTANCE.convertImportList(importVos));
            response.setContentType("application/json;charset=UTF-8");
            String tip = "";
            if (CollectionUtil.isNotEmpty(errorCntrTip)) {
                tip = " " + StringUtils.join(errorCntrTip, ",") + "箱号规则异常";
            }
            response.getOutputStream().write(JSON.toJSONString(CommonResult.success("成功导入" + importVos.size() + tip)).getBytes());
        } else {
            // 导出错误EXCEL
            String fileNameNew = fileName + LocalDateTime.now().format(FORMATTER_AMEND) + "导入错误." + fileType;
            response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileNameNew, "UTF-8"));
            response.setContentType("application/vnd.ms-excel;charset=UTF-8");
            if (importTypeCn) {
                ExcelUtils.write(response, fileNameNew, "导入数据", ManifestCntrExportCnVo.class, ManifestCntrConvert.INSTANCE.convertExportCnList(importVos));
            } else {
                ExcelUtils.write(response, fileNameNew, "导入数据", ManifestCntrExportEnVo.class, ManifestCntrConvert.INSTANCE.convertExportEnList(importVos));
            }
        }
    }

    public Map<String, CntrBaseInfoDTO> findCntrInfo(List<String> cntrNoList) {
        // 查询对应SNL中的
        if (CollectionUtil.isEmpty(cntrNoList)) {
            return new HashMap<>(16);
        }
        CommonResult<List<CntrBaseInfoDTO>> result = cntrInfoApi.queryCntrInfo(cntrNoList);
        if (CollectionUtil.isNotEmpty(result.getData())) {
            return result.getData().stream().collect(Collectors.toMap(CntrBaseInfoDTO::getCntrNo, e -> e));
        }
        return new HashMap<>(16);
    }


    public List<ManifestDO> findMasterByBookingNos(List<String> bookingNos) {
        if (CollectionUtil.isEmpty(bookingNos)) {
            return new ArrayList<>();
        }
        LambdaQueryWrapperX<ManifestDO> queryWrapperX = new LambdaQueryWrapperX<ManifestDO>();
        queryWrapperX.in(ManifestDO::getBookingNo, bookingNos);
        queryWrapperX.isNull(ManifestDO::getMasterMfNo);
        return manifestMapper.selectList(queryWrapperX);
    }

    public Map<String, ManifestCntrDO> findByManifestIdAndCntrNoMap(List<Long> manifestIds) {
        if (CollectionUtil.isEmpty(manifestIds)) {
            return new HashMap<>(16);
        }
        LambdaQueryWrapperX<ManifestCntrDO> queryWrapperX = new LambdaQueryWrapperX<ManifestCntrDO>();
        queryWrapperX.in(ManifestCntrDO::getManifestId, manifestIds);
        return manifestCntrMapper.selectList(queryWrapperX).stream().collect(Collectors.toMap(e -> e.getManifestId() + e.getCntrNo(), e -> e));
    }

    public List<ManifestDO> findByManifestNosList(List<String> manifestNos, String userId) {
        if (CollectionUtil.isEmpty(manifestNos)) {
            return new ArrayList<>();
        }
        LambdaQueryWrapperX<ManifestDO> queryWrapperX = new LambdaQueryWrapperX<ManifestDO>();
        queryWrapperX.in(ManifestDO::getManifestNo, manifestNos);
        //queryWrapperX.eq(ManifestDO::getCreator, userId);
        return manifestMapper.selectList(queryWrapperX);
    }

    public List<ManifestDO> findByBookingNoList(List<String> bookingNos, String userId) {
        if (CollectionUtil.isEmpty(bookingNos)) {
            return new ArrayList<>();
        }
        LambdaQueryWrapperX<ManifestDO> queryWrapperX = new LambdaQueryWrapperX<ManifestDO>();
        queryWrapperX.in(ManifestDO::getBookingNo, bookingNos);
        //queryWrapperX.eq(ManifestDO::getCreator, userId);
        return manifestMapper.selectList(queryWrapperX);
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    @Lock4j(name = "manifestUnique", keys = {"'updateManifestAndConfirm'", "#userId"})
    public String updateManifestAndConfirm(ManifestUpdateInfoReqVO updateReqVO, String userId) {
        updateManifest(updateReqVO, userId);
        return batchConfirm(Arrays.asList(updateReqVO.getManifest().getId()), userId, true);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @Lock4j(name = "manifestUnique", keys = {"'updateManifest'", "#userId"})
    public Long updateManifest(ManifestUpdateInfoReqVO updateReqVO, String userId) {
        checkSoNotNull(updateReqVO);
        ManifestInfoRespVO manifestInfo = getManifest(updateReqVO.getManifest().getId());
/*        if (!Objects.equals(manifestInfo.getManifest().getCreator(), userId)) {
            throw ServiceExceptionUtil.exception(ManifestErrorCodeConstants.MANIFEST_USER_LIMIT);
        }*/
        if (Objects.equals(manifestInfo.getManifest().getConfirmStatus(), ConfirmStatusEnum.Y.getValue())) {
            throw ServiceExceptionUtil.exception(ManifestErrorCodeConstants.MANIFEST_CONFIRMS_ERROR);
        }
        ManifestCargoRespVO manifestCargo = manifestInfo.getManifestCargoList().get(0);
        // 检查货物一致性
        Long errorCount = updateReqVO.getManifestCargoList().stream().filter(e -> manifestCargo.getCargoType().equals(e.getCargoType()) == false).count();
        if (errorCount > 0) {
            throw ServiceExceptionUtil.exception(ManifestErrorCodeConstants.MANIFEST_CARGO_ERROR);
        }
        // 查找主舱单中的信息
        Set<String> manifestCntrAllSet = null;
        if (StringUtils.isNotEmpty(manifestInfo.getManifest().getMasterMfNo())) {
            ManifestDO masterManifest = findMasterByBookingNo(manifestInfo.getManifest().getBookingNo());
            if (masterManifest != null) {
                manifestCntrAllSet = ManifestCntrConvert.INSTANCE.convertList(findCntrByManifestId(masterManifest.getId())).stream().map(e -> e.getCntrNo()).collect(Collectors.toSet());
            }
        }
        List<SoForecastBaseVO> soForecastCntrList = soForecastMapper.selectByBookingNo(manifestInfo.getManifest().getBookingNo());
        if (CollectionUtil.isEmpty(soForecastCntrList)) {
            throw ServiceExceptionUtil.exception(ManifestErrorCodeConstants.MANIFEST_CHECK_SO_DATA);
        }
        List<String> errorMsgList = new ArrayList<>(16);
        // 检查数据有效性
        checkForbiddenCharacter(updateReqVO.getManifestScnList(), updateReqVO.getManifestCargoList());
        checkContainer(updateReqVO, manifestInfo, errorMsgList);
        checkData(updateReqVO.getManifestCargoList(), updateReqVO.getManifestCntrList(), manifestCntrAllSet, soForecastCntrList);
        Long manifestId = updateReqVO.getManifest().getId();
        manifestMapper.updateById(ManifestConvert.INSTANCE.convert(updateReqVO.getManifest()));
        manifestCargoService.createOrUpdateAndDeleteBatch(manifestId, updateReqVO.getManifestCargoList(), manifestInfo.getManifestCargoList());
        manifestCntrService.createOrUpdateAndDeleteBatch(manifestId, updateReqVO.getManifestCntrList(), manifestInfo.getManifestCntrList());
        manifestScnService.createOrUpdateAndDeleteBatch(manifestId, updateReqVO.getManifestScnList(), manifestInfo.getManifestScnList());
        return updateReqVO.getManifest().getId();
    }

    public void checkSoNotNull(ManifestUpdateInfoReqVO updateReqVO) {
        // @Valid 不支持嵌套，改手写
        if (updateReqVO.getManifest() == null) {
            throw ServiceExceptionUtil.exception(new ErrorCode(ManifestErrorCodeConstants.MANIFEST_CHECK_NULL.getCode(), "舱单" + ManifestErrorCodeConstants.MANIFEST_CHECK_NULL.getMsg()));
        }
        if (CollectionUtil.isEmpty(updateReqVO.getManifestCntrList())) {
            throw ServiceExceptionUtil.exception(new ErrorCode(ManifestErrorCodeConstants.MANIFEST_CHECK_NULL.getCode(), "Cntr" + ManifestErrorCodeConstants.MANIFEST_CHECK_NULL.getMsg()));
        }
        if (CollectionUtil.isEmpty(updateReqVO.getManifestCargoList())) {
            throw ServiceExceptionUtil.exception(new ErrorCode(ManifestErrorCodeConstants.MANIFEST_CHECK_NULL.getCode(), "Cargo" + ManifestErrorCodeConstants.MANIFEST_CHECK_NULL.getMsg()));
        }
        if (CollectionUtil.isEmpty(updateReqVO.getManifestScnList())) {
            throw ServiceExceptionUtil.exception(new ErrorCode(ManifestErrorCodeConstants.MANIFEST_CHECK_NULL.getCode(), "Scn" + ManifestErrorCodeConstants.MANIFEST_CHECK_NULL.getMsg()));
        }
    }

    public void checkContainer(ManifestUpdateInfoReqVO updateReqVO, ManifestInfoRespVO manifestInfo, List<String> errorMsgList) {
        List<ManifestCntrUpdateReqVO> manifestCntrList = updateReqVO.getManifestCntrList();
        //检查箱号是否正确。11 位长度箱号有校验规则前四位字母后六位数字最后一位验证码也是数字。这个校验接口调用箱管的接口。
        List<String> errorList = new ArrayList<>(16);
        List<ManifestCntrLclRespVO> cntrLclList = new ArrayList<>(16);
        List<String> cntrNoList = new ArrayList<>(16);
        List<String> sizeTypeList = new ArrayList<>(16);
        manifestCntrList.forEach(e -> {
            cntrNoList.add(e.getCntrNo());
            sizeTypeList.add(e.getCntrSize() + e.getCntrType());
            if (CheckUtil.isValidCntrNo(e.getCntrNo()) == false) {
                //errorList.add(e.getCntrNo());
            }
        });
        //4、Check Container Spelling。【阻断】
        if (CollectionUtil.isNotEmpty(errorList)) {
            Assertion.assertTrue(false).raise0(ManifestErrorCodeConstants.MANIFEST_CHECK_CNTR_ERROR.getCode(), StringUtils.join(errorList, ","));
        }
        ManifestRespVO manifest = manifestInfo.getManifest();
        // 箱型、箱尺寸是否一致
        ManifestCntrLclReqVO cntrLclReq = new ManifestCntrLclReqVO();
        cntrLclReq.setCntrNoList(manifestCntrList.stream().map(e -> e.getCntrNo()).collect(Collectors.toList()));
        cntrLclReq.setBookingVesselList(Arrays.asList(manifest.getBookingVessel()));
        cntrLclReq.setBookingVoyageList(Arrays.asList(manifest.getBookingVoyage()));
        // 踢掉当前数据
        cntrLclReq.setCntrDeleteIds(manifestInfo.getManifestCntrList().stream().map(e -> e.getId()).collect(Collectors.toList()));
        // 查询同航次的箱信息
        cntrLclList.addAll(manifestMapper.selectManifestLcl(cntrLclReq));
        Map<String, List<ManifestCntrLclRespVO>> cntrMap = cntrLclList.stream().collect(Collectors.groupingBy(ManifestCntrLclRespVO::getCntrNo));
        List<String> noSameErrorList = new ArrayList<>(16);
        List<String> errorPayList = new ArrayList<>(16);
        manifestCntrList.forEach(e -> {
            List<ManifestCntrLclRespVO> checkList = cntrMap.get(e.getCntrNo());
            if (CollectionUtil.isNotEmpty(checkList)) {
                String key = e.getCntrSize() + e.getCntrType();
                //检查拼箱提单箱型、箱尺寸是否一致。
                for (ManifestCntrLclRespVO t : checkList) {
                    String checkKey = t.getCntrSize() + e.getCntrType();
                    if (Objects.equals(key, checkKey) == false) {
                        noSameErrorList.add(e.getCntrNo() + "与" + t.getManifestNo());
                    }
                }
                errorPayList.addAll(checkList.stream().filter(n -> Objects.equals(updateReqVO.getManifest().getPayment(), n.getPayment()) == false).map(n -> n.getManifestNo()).collect(Collectors.toList()));
            }
        });
        //5、Check LCL data  。
        if (CollectionUtil.isNotEmpty(noSameErrorList)) {
            Assertion.assertTrue(false).raise0(ManifestErrorCodeConstants.MANIFEST_CHECK_CNTR_TYPE_ERROR.getCode(), StringUtils.join(noSameErrorList, ","));
        }

        if (CollectionUtil.isNotEmpty(errorPayList)) {
            Assertion.assertTrue(false).raise0(ManifestErrorCodeConstants.MANIFEST_CHECK_CNTR_PAY_ERROR.getCode(), StringUtils.join(errorPayList, ","));
        }

    }

    public void checkForbiddenCharacter(List<ManifestScnUpdateReqVO> manifestScnList, List<ManifestCargoUpdateReqVO> manifestCargoList) {
        //检查提单货描（英文品名）、唛头、收发通名称、地址、邮箱、电话、传真是否含有非英文字符。
        List<String> errorList = new ArrayList<>(16);
        if (CollectionUtil.isNotEmpty(manifestScnList)) {
            for (ManifestScnUpdateReqVO scn : manifestScnList) {
                if (PartyTypeCodeEnum.SHIPPER.getValue().equals(scn.getPartyTypeCode())) {
                    if (StringUtils.isEmpty(scn.getCompanyAddress())) {
                        Assertion.assertTrue(false).raise0(ManifestErrorCodeConstants.MANIFEST_CHECK_NULL_ADRESS.getCode(), PartyTypeCodeEnum.SHIPPER.getValue());
                    }
                }
                if (PartyTypeCodeEnum.CONSIGNEE.getValue().equals(scn.getPartyTypeCode()) && scn.getCompanyName().contains("TO ORDER") == false) {
                    if (StringUtils.isEmpty(scn.getCompanyAddress())) {
                        Assertion.assertTrue(false).raise0(ManifestErrorCodeConstants.MANIFEST_CHECK_NULL_ADRESS.getCode(), PartyTypeCodeEnum.CONSIGNEE.getValue());
                    }
                }
                if (PartyTypeCodeEnum.NOTIFY.getValue().equals(scn.getPartyTypeCode()) && scn.getCompanyName().equals("SAME AS CONSIGNEE") == false) {
                    if (StringUtils.isEmpty(scn.getCompanyAddress())) {
                        Assertion.assertTrue(false).raise0(ManifestErrorCodeConstants.MANIFEST_CHECK_NULL_ADRESS.getCode(), PartyTypeCodeEnum.NOTIFY.getValue());
                    }
                }
            }
        }
        manifestCargoList.forEach(e -> {
            CheckUtil.checkString(e.getCommodityEn(), errorList);
            CheckUtil.checkString(e.getMarks(), errorList);
        });
        manifestScnList.forEach(e -> {
            CheckUtil.checkString(e.getCompanyName(), errorList);
            CheckUtil.checkString(e.getCompanyAddress(), errorList);
            CheckUtil.checkString(e.getEmail(), errorList);
            CheckUtil.checkString(e.getTelephone(), errorList);
            CheckUtil.checkString(e.getFax(), errorList);
        });
        if (CollectionUtil.isNotEmpty(errorList)) {
            Assertion.assertTrue(false).raise0(ManifestErrorCodeConstants.MANIFEST_CHECK_STR_ERROR.getCode(), StringUtils.join(errorList, ","));
        }
    }

    public void checkForbiddenCharacterConfirm(ManifestDO manifest, List<ManifestScnDO> manifestScnList, List<ManifestCargoDO> manifestCargoList, List<BlnoScnDO> blnoScnList, BlnoCargoDO blnoCargo, List<String> tipInfoMsg) {
        //检查提单货描（英文品名）、唛头、收发通名称、地址、邮箱、电话、传真是否含有非英文字符。
        List<String> errorList = new ArrayList<>(16);
        Map<String, BlnoScnDO> blnoScnMap = new HashMap(16);
        if (CollectionUtil.isNotEmpty(blnoScnList)) {
            blnoScnMap = blnoScnList.stream().collect(Collectors.toMap(e -> e.getPartyTypeCode(), e -> e));
        }
        if (CollectionUtil.isNotEmpty(manifestScnList)) {
            for (ManifestScnDO scn : manifestScnList) {
                if (PartyTypeCodeEnum.SHIPPER.getValue().equals(scn.getPartyTypeCode())) {
                    if (StringUtils.isEmpty(scn.getCompanyAddress())) {
                        Assertion.assertTrue(false).raise0(ManifestErrorCodeConstants.MANIFEST_CHECK_NULL_ADRESS.getCode(), manifest.getManifestNo() + " " + PartyTypeCodeEnum.SHIPPER.getValue());
                    }
                    BlnoScnDO blnoScn = blnoScnMap.get(PartyTypeCodeEnum.SHIPPER.getValue());
                    if (blnoScn != null && Objects.equals(blnoScn.getCompanyName(), scn.getCompanyName()) == false) {
                        tipInfoMsg.add(manifest.getManifestNo() + "与SI中发货人名称不一致");
                    }
                }
                if (PartyTypeCodeEnum.CONSIGNEE.getValue().equals(scn.getPartyTypeCode())) {
                    if (StringUtils.isEmpty(scn.getCompanyAddress()) && scn.getCompanyName().contains("TO ORDER") == false) {
                        Assertion.assertTrue(false).raise0(ManifestErrorCodeConstants.MANIFEST_CHECK_NULL_ADRESS.getCode(), manifest.getManifestNo() + " " + PartyTypeCodeEnum.CONSIGNEE.getValue());
                    }
                    BlnoScnDO blnoScn = blnoScnMap.get(PartyTypeCodeEnum.CONSIGNEE.getValue());
                    if (blnoScn != null && Objects.equals(blnoScn.getCompanyName(), scn.getCompanyName()) == false) {
                        tipInfoMsg.add(manifest.getManifestNo() + "与SI中收货人名称不一致");
                    }
                }
                if (PartyTypeCodeEnum.NOTIFY.getValue().equals(scn.getPartyTypeCode())) {
                    if (StringUtils.isEmpty(scn.getCompanyAddress()) && scn.getCompanyName().equals("SAME AS CONSIGNEE") == false) {
                        Assertion.assertTrue(false).raise0(ManifestErrorCodeConstants.MANIFEST_CHECK_NULL_ADRESS.getCode(), manifest.getManifestNo() + " " + PartyTypeCodeEnum.NOTIFY.getValue());
                    }
                    BlnoScnDO blnoScn = blnoScnMap.get(PartyTypeCodeEnum.NOTIFY.getValue());
                    if (blnoScn != null && Objects.equals(blnoScn.getCompanyName(), scn.getCompanyName()) == false) {
                        tipInfoMsg.add(manifest.getManifestNo() + "与SI中通知人名称不一致");
                    }
                }
            }
        }
        boolean check = false;
        for (ManifestCargoDO e : manifestCargoList) {
            CheckUtil.checkString(e.getCommodityEn(), errorList);
            CheckUtil.checkString(e.getMarks(), errorList);
            if (blnoCargo != null && Objects.equals(blnoCargo.getCommodityEn(), e.getCommodityEn()) == false) {
                check = true;
            }
        }
        if (check) {
            tipInfoMsg.add(manifest.getManifestNo() + "与SI中货描不一致");
        }
        manifestScnList.forEach(e -> {
            CheckUtil.checkString(e.getCompanyName(), errorList);
            CheckUtil.checkString(e.getCompanyAddress(), errorList);
            CheckUtil.checkString(e.getEmail(), errorList);
            CheckUtil.checkString(e.getTelephone(), errorList);
            CheckUtil.checkString(e.getFax(), errorList);
        });
        if (CollectionUtil.isNotEmpty(errorList)) {
            Assertion.assertTrue(false).raise0(ManifestErrorCodeConstants.MANIFEST_CHECK_STR_ERROR.getCode(), manifest.getManifestNo() + " " + StringUtils.join(errorList, ","));
        }
    }

    public void checkContainerConfirm(ManifestDO manifest, List<ManifestCntrDO> manifestCntrList, List<String> tipInfoMsg) {
        //检查箱号是否正确。11 位长度箱号有校验规则前四位字母后六位数字最后一位验证码也是数字。这个校验接口调用箱管的接口。
        List<String> errorList = new ArrayList<>(16);
        List<ManifestCntrLclRespVO> cntrLclList = new ArrayList<>(16);
        List<String> cntrNoList = new ArrayList<>(16);
        List<String> sizeTypeList = new ArrayList<>(16);
        manifestCntrList.forEach(e -> {
            cntrNoList.add(e.getCntrNo());
            sizeTypeList.add(e.getCntrSize() + e.getCntrType());
            if (CheckUtil.isValidCntrNo(e.getCntrNo()) == false) {
                //errorList.add(e.getCntrNo());
            }
        });
        //4、Check Container Spelling。【阻断】
        if (CollectionUtil.isNotEmpty(errorList)) {
            Assertion.assertTrue(false).raise0(ManifestErrorCodeConstants.MANIFEST_CHECK_CNTR_ERROR.getCode(), manifest.getManifestNo() + "中" + StringUtils.join(errorList, ","));
        }
        // 箱型、箱尺寸是否一致
        ManifestCntrLclReqVO cntrLclReq = new ManifestCntrLclReqVO();
        cntrLclReq.setCntrNoList(manifestCntrList.stream().map(e -> e.getCntrNo()).collect(Collectors.toList()));
        cntrLclReq.setBookingVesselList(Arrays.asList(manifest.getBookingVessel()));
        cntrLclReq.setBookingVoyageList(Arrays.asList(manifest.getBookingVoyage()));
        // 踢掉当前数据
        cntrLclReq.setCntrDeleteIds(manifestCntrList.stream().map(e -> e.getId()).collect(Collectors.toList()));
        // 查询同航次的箱信息
        cntrLclList.addAll(manifestMapper.selectManifestLcl(cntrLclReq));
        Map<String, List<ManifestCntrLclRespVO>> cntrMap = cntrLclList.stream().collect(Collectors.groupingBy(ManifestCntrLclRespVO::getCntrNo));
        List<String> noSameErrorList = new ArrayList<>(16);
        List<String> errorPayList = new ArrayList<>(16);
        manifestCntrList.forEach(e -> {
            List<ManifestCntrLclRespVO> checkList = cntrMap.get(e.getCntrNo());
            if (CollectionUtil.isNotEmpty(checkList)) {
                String key = e.getCntrSize() + e.getCntrType();
                //检查拼箱提单箱型、箱尺寸是否一致。
                for (ManifestCntrLclRespVO t : checkList) {
                    String checkKey = t.getCntrSize() + e.getCntrType();
                    if (Objects.equals(key, checkKey) == false) {
                        noSameErrorList.add(e.getCntrNo() + "与" + t.getManifestNo());
                    }
                }
                errorPayList.addAll(checkList.stream().filter(n -> Objects.equals(manifest.getPayment(), n.getPayment()) == false).map(n -> n.getManifestNo()).collect(Collectors.toList()));
            }
        });
        //5、Check LCL data  。
        if (CollectionUtil.isNotEmpty(noSameErrorList)) {
            Assertion.assertTrue(false).raise0(ManifestErrorCodeConstants.MANIFEST_CHECK_CNTR_TYPE_ERROR.getCode(), manifest.getManifestNo() + " " + StringUtils.join(noSameErrorList, ","));
        }

        if (CollectionUtil.isNotEmpty(errorPayList)) {
            Assertion.assertTrue(false).raise0(ManifestErrorCodeConstants.MANIFEST_CHECK_CNTR_PAY_ERROR.getCode(), manifest.getManifestNo() + " " + StringUtils.join(errorPayList, ","));
        }
        //检查提示
        Map<String, CntrBaseInfoDTO> cntrBaseInfoMap = findCntrInfo(cntrNoList);
        List<String> commonCntrTypeList = cntrTransferService.getCntrTypeStringListExist(sizeTypeList);
        manifestCntrList.forEach(e -> {
            String cntrSizeType = e.getCntrSize() + e.getCntrType();
            if (commonCntrTypeList.contains(cntrSizeType) == false) {
                tipInfoMsg.add(e.getCntrNo() + "尺寸类型" + cntrSizeType + "未匹配");
            }
            if (e.getCntrOwner().equals(CntrOwnerEnum.SNL.getValue())) {
                CntrBaseInfoDTO cntrInfo = cntrBaseInfoMap.get(e.getCntrNo());
                if (cntrInfo != null) {
                    if (Objects.equals(cntrInfo.getCntrSize(), e.getCntrSize()) == false || Objects.equals(cntrInfo.getCntrType(), e.getCntrType()) == false) {
                        tipInfoMsg.add(e.getCntrNo() + "尺寸箱型" + e.getCntrSize() + "未匹配SNL");
                    }
                } else {
                    tipInfoMsg.add(e.getCntrNo() + "未匹配到SNL");
                }
            }
        });
    }

    public void checkData(List<ManifestCargoUpdateReqVO> manifestCargoList, List<ManifestCntrUpdateReqVO> manifestCntrList, Set<String> manifestCntrAllSet, List<SoForecastBaseVO> soForecastCntrList) {
        Set<String> cntrNoSet = new HashSet<>(16);
        Map<String, SoForecastBaseVO> soForecastCntrMap = soForecastCntrList.stream().collect(Collectors.toMap(e -> e.getCntrSize() + e.getCntrType(), e -> e));
        for (ManifestCntrUpdateReqVO e : manifestCntrList) {
            String key = e.getCntrSize() + e.getCntrType();
            SoForecastBaseVO forecast = soForecastCntrMap.get(key);
            if (forecast == null) {
                //匹配SO的预配信息
                Assertion.assertTrue(false).raise0(ManifestErrorCodeConstants.MANIFEST_CHECK_SO_CNTR_DATA.getCode(), key);
            } else {
                forecast.setCntrQuantity(forecast.getCntrQuantity() - 1);
            }
            if (manifestCntrAllSet != null && manifestCntrAllSet.contains(e.getCntrNo()) == false) {
                // 箱不在主舱单中
                Assertion.assertTrue(false).raise0(ManifestErrorCodeConstants.MANIFEST_CHECK_MAIN_DATA.getCode(), e.getCntrNo());
            }
            if (cntrNoSet.contains(e.getCntrNo())) {
                // 箱重复
                Assertion.assertTrue(false).raise0(ManifestErrorCodeConstants.MANIFEST_CHECK_DATA_SAME.getCode(), e.getCntrNo());
            } else {
                cntrNoSet.add(e.getCntrNo());
            }
        }

        for (SoForecastBaseVO f : soForecastCntrMap.values()) {
            if (f.getCntrQuantity() < 0) {
                //不能超过SO的预配箱数量
                Assertion.assertTrue(false).raise0(ManifestErrorCodeConstants.MANIFEST_CHECK_SO_CNTR_NUM_DATA.getCode(), f.getCntrSize() + f.getCntrType());
            }
        }

        for (ManifestCargoUpdateReqVO e : manifestCargoList) {
            if (CollectionUtil.isNotEmpty(e.getCntrNoList())) {
                List<String> notExistList = e.getCntrNoList().stream().filter(t -> !cntrNoSet.contains(t.getCntrNo())).map(t -> t.getCntrNo()).collect(Collectors.toList());
                if (CollectionUtil.isNotEmpty(notExistList)) {
                    //货物的箱与箱数据不一致
                    Assertion.assertTrue(false).raise0(ManifestErrorCodeConstants.MANIFEST_CHECK_DATA.getCode(), e.getCommodityCn() + "货物的箱" + StringUtils.join(notExistList, ","));
                }
                Map<String, Long> countMap = e.getCntrNoList().stream().collect(Collectors.groupingBy(ManifestCargoCntrUpdateReqVO::getCntrNo, Collectors.counting()));
                List<String> errorCntrList = new ArrayList<>(16);
                for (Map.Entry<String, Long> entry : countMap.entrySet()) {
                    if (entry.getValue() > 1) {
                        errorCntrList.add(entry.getKey());
                    }
                }
                if (CollectionUtil.isNotEmpty(errorCntrList)) {
                    //货物的箱数据重复
                    Assertion.assertTrue(false).raise0(ManifestErrorCodeConstants.MANIFEST_CHECK_DATA_SAME.getCode(), "货物的箱" + StringUtils.join(errorCntrList, ","));
                }
            }
        }
    }

    public String getI18Msg(ErrorCode error) {
        Locale currentLocale = LocaleContextHolder.getLocale();
        return i18nsUtil.getMessage(error.getCode(), currentLocale);
    }

    @Override
    public List<String> checkCargoCntrData(ManifestUpdateInfoReqVO updateReqVO) {
        Locale currentLocale = LocaleContextHolder.getLocale();
        i18nsUtil.getMessage(SpecialCargoFlexiErrorCodeConstants.SPECIAL_CARGO_FLEXI_CNTR_SIZE_ERROR.getCode(), currentLocale);
        List<ManifestCargoUpdateReqVO> manifestCargoList = updateReqVO.getManifestCargoList();
        List<ManifestCntrUpdateReqVO> manifestCntrList = updateReqVO.getManifestCntrList();
        List<String> tipMsgList = new ArrayList<>(16);
        BigDecimal grossWeightCntrSum = BigDecimal.ZERO;
        Long quantityCntrSum = 0L;
        BigDecimal measurementCntrSum = BigDecimal.ZERO;
        List<String> errorCntrNoList = new ArrayList<>(16);
        // 判断导入的数据类型
        List<String> sizeTypeList = new ArrayList<>();
        // 检查箱信息
        List<String> cntrNoList = new ArrayList<>();
        for (ManifestCntrUpdateReqVO e : manifestCntrList) {
            grossWeightCntrSum = grossWeightCntrSum.add(e.getGrossWeight());
            quantityCntrSum += e.getQuantity();
            measurementCntrSum = measurementCntrSum.add(e.getMeasurement());
            if (CheckUtil.isValidCntrNo(e.getCntrNo()) == false) {
                errorCntrNoList.add(e.getCntrNo());
            }
            cntrNoList.add(e.getCntrNo());
            sizeTypeList.add(e.getCntrSize() + e.getCntrType());
        }
        if (CollectionUtil.isNotEmpty(errorCntrNoList)) {
            tipMsgList.add(StringUtils.join(errorCntrNoList, ",") + getI18Msg(ManifestErrorCodeConstants.MANIFEST_CHECK_CNTR_RULE_ERROR));
        }
        BigDecimal grossWeightSum = BigDecimal.ZERO;
        Long quantitySum = 0L;
        BigDecimal measurementSum = BigDecimal.ZERO;
        for (ManifestCargoUpdateReqVO e : manifestCargoList) {
            grossWeightSum = grossWeightSum.add(e.getGrossWeight());
            quantitySum += e.getQuantity();
            measurementSum = measurementSum.add(e.getMeasurement());
        }
        //数据总的校验
        if (grossWeightSum.compareTo(grossWeightCntrSum) != 0) {
            tipMsgList.add(getI18Msg(ManifestErrorCodeConstants.MANIFEST_CHECK_DATA_CARGO_WEIGHT));
        }
        if (Objects.equals(quantitySum, quantityCntrSum) == false) {
            tipMsgList.add(getI18Msg(ManifestErrorCodeConstants.MANIFEST_CHECK_DATA_CARGO_QUANTITY));
        }
        if (measurementSum.compareTo(measurementCntrSum) != 0) {
            tipMsgList.add(getI18Msg(ManifestErrorCodeConstants.MANIFEST_CHECK_DATA_CARGO_MEASUREMENT));
        }
        //检查提示
        Map<String, CntrBaseInfoDTO> cntrBaseInfoMap = findCntrInfo(cntrNoList);
        List<String> commonCntrTypeList = cntrTransferService.getCntrTypeStringListExist(sizeTypeList);
        manifestCntrList.forEach(e -> {
            String cntrSizeType = e.getCntrSize() + e.getCntrType();
            if (commonCntrTypeList.contains(cntrSizeType) == false) {
                tipMsgList.add(cntrSizeType + "尺寸类型未匹配");
            }
            if (e.getCntrOwner().equals(CntrOwnerEnum.SNL.getValue())) {
                CntrBaseInfoDTO cntrInfo = cntrBaseInfoMap.get(e.getCntrNo());
                if (cntrInfo != null) {
                    if (Objects.equals(cntrInfo.getCntrSize(), e.getCntrSize()) == false || Objects.equals(cntrInfo.getCntrType(), e.getCntrType()) == false) {
                        tipMsgList.add(e.getCntrSize() + cntrInfo.getCntrType() + "尺寸箱型未匹配SNL");
                    }
                } else {
                    tipMsgList.add(e.getCntrNo() + "未匹配到SNL");
                }
            }
        });
        if (CollectionUtil.isEmpty(tipMsgList)) {
            return null;
        }
        return tipMsgList;
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    @Lock4j(name = "manifestUnique", keys = {"'deleteManifestBatch'", "#userId"})
    public void deleteManifestBatch(List<Long> ids, String userId) {
        List<ManifestDO> list = checkByUserId(ids, ConfirmStatusEnum.N.getValue(), userId);
        // 删除
        manifestMapper.deleteBatchIds(ids);
        deleteCargaoByManifestIds(ids);
        deleteScnByManifestIds(ids);
        deleteCntrByManifestIds(ids);
    }

    @Override
    public ManifestCancelVO checkCancel(List<Long> ids, String userId) {
        ManifestCancelVO result = new ManifestCancelVO(new ArrayList<>(16), new ArrayList<>(16));
        List<String> bookingNoList = checkCancelStatus(ids, userId);
        LambdaQueryWrapper<ManifestDO> queryWrapperX = new LambdaQueryWrapper<ManifestDO>();
        queryWrapperX.in(ManifestDO::getBookingNo, bookingNoList);
        queryWrapperX.orderByAsc(ManifestDO::getManifestNo);
        List<ManifestDO> manifestList = manifestMapper.selectList(queryWrapperX);
        for (ManifestDO manifest : manifestList) {
            if (StringUtils.isEmpty(manifest.getMasterMfNo())) {
                result.getMainList().add(manifest.getManifestNo());
            } else {
                result.getSalveList().add(manifest.getManifestNo());
            }
        }
        return result;
    }

    public List<String> checkCancelStatus(List<Long> ids, String userId) {
        // 检查是否可以取消,后续需要查询EDI 已经确认还没有发送edi的预配舱单，确认人可以进行取消确认操作。
        // 总单和分单必须同时取消确认。可以选中分单操作也可以选中总单操作
        List<ManifestDO> list = checkByUserId(ids, ConfirmStatusEnum.Y.getValue(), userId);
        List<String> bookingNoList = list.stream().map(e -> e.getBookingNo()).distinct().collect(Collectors.toList());
        return bookingNoList;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @Lock4j(name = "manifestUnique", keys = {"'cancelConfirm'", "#userId"})
    public void cancelConfirm(List<Long> ids, String userId) {
        List<String> bookingNoList = checkCancelStatus(ids, userId);
        LambdaUpdateWrapper<ManifestDO> queryWrapperX = new LambdaUpdateWrapper<ManifestDO>();
        queryWrapperX.in(ManifestDO::getBookingNo, bookingNoList);
        // queryWrapperX.eq(ManifestDO::getCreator, userId);
        queryWrapperX.set(ManifestDO::getConfirmStatus, ConfirmStatusEnum.N.getValue());
        queryWrapperX.set(ManifestDO::getUpdater, userId);
        queryWrapperX.set(ManifestDO::getUpdateTime, LocalDateTime.now());
        manifestMapper.update(null, queryWrapperX);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @Lock4j(name = "manifestUnique", keys = {"'updateTransitTerm'", "#userId"})
    public void updateTransitTerm(ManifestTransitTermReqVO transitTermReq, String userId) {
        if (CollectionUtil.isEmpty(transitTermReq.getManifestIdList())) {
            return;
        }
        LambdaUpdateWrapper<ManifestDO> queryWrapperX = new LambdaUpdateWrapper<ManifestDO>();
        queryWrapperX.in(ManifestDO::getId, transitTermReq.getManifestIdList());
        //queryWrapperX.eq(ManifestDO::getCreator, userId);
        queryWrapperX.set(ManifestDO::getTransitTerm, transitTermReq.getTransitTerm());
        queryWrapperX.set(ManifestDO::getUpdater, userId);
        queryWrapperX.set(ManifestDO::getUpdateTime, LocalDateTime.now());
        manifestMapper.update(null, queryWrapperX);
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    @Lock4j(name = "manifestUnique", keys = {"'batchConfirm'", "#userId"})
    public String batchConfirm(List<Long> ids, String userId, boolean confirm) {
        List<String> tipInfoMsg = new ArrayList<>(16);
        // 检查状态是否正常
        List<ManifestDO> list = checkByUserId(ids, ConfirmStatusEnum.N.getValue(), userId);
        List<String> bookingNoList = list.stream().map(e -> e.getBookingNo()).distinct().collect(Collectors.toList());
        List<ManifestDO> allList = findByBookingNos(bookingNoList);
        List<Long> manifestIds = allList.stream().map(e -> e.getId()).distinct().collect(Collectors.toList());
        Map<Long, List<ManifestScnDO>> scnMap = findScnByManifestId(manifestIds);
        Map<Long, List<ManifestCntrDO>> cntrMap = findCntrByManifestId(manifestIds);
        List<ManifestCargoDO> cargoAllList = findCargoByManifestId(manifestIds);
        Map<Long, List<ManifestCargoDO>> cargoMap = cargoAllList.stream().collect(Collectors.groupingBy(ManifestCargoDO::getManifestId));
        List<Long> cargoAllIds = cargoAllList.stream().map(e -> e.getId()).collect(Collectors.toList());
        Map<Long, List<ManifestCargoCntrDO>> cargoCntrMap = getCargoCntrMap(cargoAllIds);

        // 检查SI数据
        List<String> splitCombineTypeList = Arrays.asList(SiSplitCombineEnum.NORMAL.getValue(), SiSplitCombineEnum.NORMAL_SPLIT.getValue(), SiSplitCombineEnum.NORMAL_COMBINE.getValue());
        List<ManifestCntrSumCheckVO> blnoList = blnoMapper.selectSumByBookingNo(bookingNoList, splitCombineTypeList);
        Map<String, Long> blnoBookingMap = new HashMap<>(16);
        Map<Long, List<BlnoScnDO>> blnoScnMap = new HashMap<>(16);
        Map<Long, List<BlnoCargoDO>> blnoCargoMap = new HashMap<>(16);
        if (CollectionUtil.isNotEmpty(blnoList)) {
            List<Long> blnoIds = blnoList.stream().map(e -> e.getBlNoId()).collect(Collectors.toList());
            blnoBookingMap = blnoList.stream().collect(Collectors.toMap(e -> e.getBookingNo(), e -> e.getBlNoId()));
            blnoScnMap = getBlnoScnByBlnoIdMap(blnoIds);
            blnoCargoMap = getBlnoCargoByBlnoIdMap(blnoIds);
        }
        // 检查货物信息的件重尺和箱信息的件重尺是否一致
        for (ManifestDO manifest : allList) {
            List<ManifestCntrDO> cntrList = cntrMap.get(manifest.getId());
            List<ManifestCargoDO> cargoList = cargoMap.get(manifest.getId());
            List<ManifestScnDO> scnList = scnMap.get(manifest.getId());
            Long blnoId = blnoBookingMap.get(manifest.getBookingNo());
            List<BlnoScnDO> blnoScnList = new ArrayList<>(8);
            BlnoCargoDO blnoCargo = null;
            if (blnoId != null) {
                blnoScnList = blnoScnMap.get(blnoId);
                List<BlnoCargoDO> blnoCargoList = blnoCargoMap.get(blnoId);
                if (CollectionUtil.isNotEmpty(blnoCargoList)) {
                    blnoCargo = blnoCargoList.get(0);
                }
            }
            // 数据完整校验
            if (CollectionUtil.isEmpty(cntrList)) {
                Assertion.assertTrue(false).raise0(ManifestErrorCodeConstants.MANIFEST_CHECK_NULL_DATA.getCode(), manifest.getManifestNo() + "箱信息");
            }
            if (CollectionUtil.isEmpty(cargoList)) {
                Assertion.assertTrue(false).raise0(ManifestErrorCodeConstants.MANIFEST_CHECK_NULL_DATA.getCode(), manifest.getManifestNo() + "货物信息");
            }
            if (CollectionUtil.isEmpty(scnList) || scnList.size() < 3) {
                Assertion.assertTrue(false).raise0(ManifestErrorCodeConstants.MANIFEST_CHECK_NULL_DATA.getCode(), manifest.getManifestNo() + "相关方信息");
            }
            checkForbiddenCharacterConfirm(manifest, scnList, cargoList, blnoScnList, blnoCargo, tipInfoMsg);
            checkContainerConfirm(manifest, cntrList, tipInfoMsg);
            //检查数量
            List<String> cntrListStr = cntrList.stream().map(e -> e.getCntrNo()).collect(Collectors.toList());
            BigDecimal grossWeightSum = BigDecimal.ZERO;
            BigDecimal measurementSum = BigDecimal.ZERO;
            Long quantitySum = 0L;
            for (ManifestCargoDO cargo : cargoList) {
                grossWeightSum = grossWeightSum.add(cargo.getGrossWeight());
                quantitySum += cargo.getQuantity();
                measurementSum = measurementSum.add(cargo.getMeasurement());
                List<ManifestCargoCntrDO> cargoCntrList = cargoCntrMap.get(cargo.getId());
                if (CollectionUtil.isNotEmpty(cargoCntrList)) {
                    for (ManifestCargoCntrDO cargoCntr : cargoCntrList) {
                        if (cntrListStr.contains(cargoCntr.getCntrNo()) == false) {
                            Assertion.assertTrue(false).raise0(ManifestErrorCodeConstants.MANIFEST_CNTR_ALL_ERROR.getCode(), manifest.getManifestNo() + "货物的" + cargoCntr.getCntrNo() + "在箱信息不存在,");
                        }
                    }
                }
            }
            BigDecimal grossWeightCntrSum = BigDecimal.ZERO;
            BigDecimal measurementCntrSum = BigDecimal.ZERO;
            Long quantityCntrSum = 0L;
            for (ManifestCntrDO cntr : cntrList) {
                grossWeightCntrSum = grossWeightCntrSum.add(cntr.getGrossWeight());
                quantityCntrSum += cntr.getQuantity();
                measurementCntrSum = measurementCntrSum.add(cntr.getMeasurement());
            }
            //数据总的校验
            if (grossWeightSum.compareTo(grossWeightCntrSum) != 0) {
                tipInfoMsg.add(manifest.getManifestNo() + "与箱的货物毛重不一致");
            }
            if (Objects.equals(quantitySum, quantityCntrSum) == false) {
                tipInfoMsg.add(manifest.getManifestNo() + "与箱的货物件数与SI不一致");
            }
            if (measurementSum.compareTo(measurementCntrSum) != 0) {
                tipInfoMsg.add(manifest.getManifestNo() + "与箱的货物体积与SI不一致");
            }
        }
        List<String> errorMsgList = new ArrayList<>(16);
        // 检查
        Map<String, ManifestCntrSumCheckVO> siSumAllMap = blnoList.stream().collect(Collectors.toMap(ManifestCntrSumCheckVO::getBookingNo, e -> e));
        Map<String, List<ManifestCntrSumCheckVO>> mfSumAllMap = manifestCntrMapper.selectMfSumByBookingNo(bookingNoList).stream().collect(Collectors.groupingBy(ManifestCntrSumCheckVO::getBookingNo));
        Map<String, List<ManifestCntrSumCheckVO>> masterSumAllMap = manifestCntrMapper.selectMasterSumByBookingNo(bookingNoList).stream().collect(Collectors.groupingBy(ManifestCntrSumCheckVO::getBookingNo));
        for (String bookingNo : bookingNoList) {
            List<ManifestCntrSumCheckVO> mfSumList = mfSumAllMap.get(bookingNo);
            List<ManifestCntrSumCheckVO> masterSumList = masterSumAllMap.get(bookingNo);
            ManifestCntrSumCheckVO siSum = siSumAllMap.get(bookingNo);
            // 预配舱单【总单】提交的时候需要增加三单一致。预配舱单总单的货物的件重尺（如果预配舱单有多个货类需要合计件重尺）
            // 和 SI【拆并前的提单，如果提单不存在则不校验】的货物的件重尺一致。【提示】
            if (masterSumList == null) {
                errorMsgList.add(bookingNo + "箱信息不能为空");
                continue;
            }
            if (siSum != null) {
                Long quantitySum = 0L;
                BigDecimal grossWeightSum = BigDecimal.ZERO;
                BigDecimal measurementSum = BigDecimal.ZERO;
                for (ManifestCntrSumCheckVO e : masterSumList) {
                    quantitySum += e.getQuantitySum();
                    grossWeightSum = grossWeightSum.add(e.getGrossWeightSum());
                    measurementSum = measurementSum.add(e.getMeasurementSum());
                }
                if (Objects.equals(siSum.getQuantitySum(), quantitySum) == false) {
                    tipInfoMsg.add(bookingNo + "货物件数与SI不一致");
                }
                if (siSum.getGrossWeightSum().compareTo(grossWeightSum) != 0) {
                    tipInfoMsg.add(bookingNo + "货物毛重与SI不一致");
                }
                if (siSum.getMeasurementSum().compareTo(measurementSum) != 0) {
                    tipInfoMsg.add(bookingNo + "货物体积与SI不一致");
                }
            }
            // 没有子舱单，直接通过
            if (mfSumList == null) {
                continue;
            }
            Map<String, List<ManifestCntrSumCheckVO>> mfSumMap = mfSumList.stream().collect(Collectors.groupingBy(ManifestCntrSumCheckVO::getCntrNo));
            Map<String, ManifestCntrSumCheckVO> masterSumMap = masterSumList.stream().collect(Collectors.toMap(ManifestCntrSumCheckVO::getCntrNo, e -> e));
            for (ManifestCntrSumCheckVO master : masterSumMap.values()) {
                List<ManifestCntrSumCheckVO> slaveList = mfSumMap.get(master.getCntrNo());
                if (slaveList == null) {
                    errorMsgList.add(bookingNo + "的" + master.getCntrNo() + "箱在拆分中未匹配");
                    continue;
                }
                if (slaveList.size() > 1) {
                    errorMsgList.add(bookingNo + "的" + master.getCntrNo() + "箱拆分不一致");
                }
                String checkSame = CompareUtils.compareByFieldsString(master, slaveList.get(0), ManifestCntrSumCheckVO.getMultiFieldComparatorCombine());
                if (StringUtils.isNotEmpty(checkSame)) {
                    errorMsgList.add(bookingNo + checkSame + "数据不一致");
                }
                //匹配的剔除
                mfSumMap.remove(master.getCntrNo());
            }
            for (String cntrNo : mfSumMap.keySet()) {
                errorMsgList.add(bookingNo + "的" + cntrNo + "箱不存在");
            }
        }
        if (CollectionUtil.isNotEmpty(errorMsgList)) {
            Assertion.assertTrue(false).raise0(ManifestErrorCodeConstants.MANIFEST_CNTR_SUM_ERROR.getCode(), StringUtils.join(errorMsgList, ","));
        } else {
            // 目前校验规则 提示和阻断是在一起的，需要分离
            if (confirm == true) {
                LambdaUpdateWrapper<ManifestDO> queryWrapperX = new LambdaUpdateWrapper<ManifestDO>();
                queryWrapperX.in(ManifestDO::getBookingNo, bookingNoList);
                queryWrapperX.set(ManifestDO::getConfirmStatus, ConfirmStatusEnum.Y.getValue());
                queryWrapperX.set(ManifestDO::getUpdater, userId);
                queryWrapperX.set(ManifestDO::getUpdateTime, LocalDateTime.now());
                manifestMapper.update(null, queryWrapperX);
            }
        }
        if (CollectionUtil.isNotEmpty(tipInfoMsg)) {
            return StringUtils.join(tipInfoMsg, ";");
        }
        return null;
    }

    public Map<Long, List<BlnoScnDO>> getBlnoScnByBlnoIdMap(Collection<Long> ids) {
        LambdaQueryWrapperX<BlnoScnDO> queryWrapperX = new LambdaQueryWrapperX<>();
        queryWrapperX.in(BlnoScnDO::getBlnoId, ids);
        return blnoScnMapper.selectList(queryWrapperX).stream().collect(Collectors.groupingBy(e -> e.getBlnoId()));
    }

    public Map<Long, List<BlnoCargoDO>> getBlnoCargoByBlnoIdMap(Collection<Long> ids) {
        LambdaQueryWrapperX<BlnoCargoDO> queryWrapperX = new LambdaQueryWrapperX<>();
        queryWrapperX.in(BlnoCargoDO::getBlnoId, ids);
        return blnoCargoMapper.selectList(queryWrapperX).stream().collect(Collectors.groupingBy(e -> e.getBlnoId()));
    }


    public List<ManifestDO> checkByUserId(List<Long> ids, String confirmStatus, String userId) {
        if (CollectionUtil.isEmpty(ids)) {
            throw ServiceExceptionUtil.exception(ManifestErrorCodeConstants.MANIFEST_NOT_EXISTS);
        }
        //查询状态，默认只能操作自己的
        LambdaQueryWrapperX<ManifestDO> queryWrapperX = new LambdaQueryWrapperX<ManifestDO>();
        queryWrapperX.in(ManifestDO::getId, ids);
        List<ManifestDO> list = manifestMapper.selectList(queryWrapperX);
        if (ids.size() != list.size()) {
            throw ServiceExceptionUtil.exception(ManifestErrorCodeConstants.MANIFEST_PART_NOT_EXISTS);
        } else {
/*            List<String> errorBookingNos = list.stream().filter(e -> !Objects.equals(e.getCreator(), userId)).map(e -> e.getBookingNo()).distinct().collect(Collectors.toList());
            if (CollectionUtil.isNotEmpty(errorBookingNos)) {
                throw ServiceExceptionUtil.exception(new ErrorCode(ManifestErrorCodeConstants.MANIFEST_USER_LIMIT.getCode(), StringUtils.join(errorBookingNos, ",") + ManifestErrorCodeConstants.MANIFEST_USER_LIMIT.getMsg()));
            }*/
            if (StringUtils.isNotEmpty(confirmStatus)) {
                List<String> errorConfirms = list.stream().filter(e -> !Objects.equals(e.getConfirmStatus(), confirmStatus)).map(e -> e.getBookingNo()).distinct().collect(Collectors.toList());
                if (CollectionUtil.isNotEmpty(errorConfirms)) {
                    throw ServiceExceptionUtil.exception(new ErrorCode(ManifestErrorCodeConstants.MANIFEST_CONFIRMS_LIMIT.getCode(), StringUtils.join(errorConfirms, ",") + ManifestErrorCodeConstants.MANIFEST_CONFIRMS_LIMIT.getMsg()));
                }
            }
        }
        return list;
    }

    public void deleteCargaoByManifestIds(List<Long> manifestIds) {
        LambdaUpdateWrapper<ManifestCargoDO> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.in(ManifestCargoDO::getManifestId, manifestIds);
        manifestCargoMapper.delete(updateWrapper);
    }

    public void deleteScnByManifestIds(List<Long> manifestIds) {
        LambdaUpdateWrapper<ManifestScnDO> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.in(ManifestScnDO::getManifestId, manifestIds);
        manifestScnMapper.delete(updateWrapper);
    }

    public void deleteCntrByManifestIds(List<Long> manifestIds) {
        LambdaUpdateWrapper<ManifestCntrDO> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.in(ManifestCntrDO::getManifestId, manifestIds);
        manifestCntrMapper.delete(updateWrapper);
    }

    @Override
    public ManifestInfoRespVO getManifestDetailByBookingNo(String bookingNo) {
        ManifestDO master = findMasterByBookingNo(bookingNo);
        if (master != null) {
            return getManifest(master.getId());
        }
        return null;
    }

    @Override
    public ManifestInfoRespVO getManifest(Long id) {
        ManifestInfoRespVO manifestInfo = new ManifestInfoRespVO();
        ManifestDO manifest = manifestMapper.selectById(id);
        if (manifest == null) {
            throw ServiceExceptionUtil.exception(ManifestErrorCodeConstants.MANIFEST_NOT_EXISTS);
        }
        manifestInfo.setManifest(ManifestConvert.INSTANCE.convert(manifest));
        manifestInfo.setManifestCargoList(ManifestCargoConvert.INSTANCE.convertList(findCargoByManifestId(manifest.getId())));
        manifestInfo.setManifestScnList(ManifestScnConvert.INSTANCE.convertList(findScnByManifestId(manifest.getId())));
        manifestInfo.setManifestCntrList(ManifestCntrConvert.INSTANCE.convertList(findCntrByManifestId(manifest.getId())));
        manifestInfo.setManifestVslvoyList(ManifestVslvoyConvert.INSTANCE.convertList(findVslvoyByManifestId(manifest.getId())));
        manifestInfo.setSoForecastList(SoForecastConvert.INSTANCE.convertListBase(soForecastMapper.selectByBookingNo(manifestInfo.getManifest().getBookingNo())));

        if (CollectionUtil.isNotEmpty(manifestInfo.getManifestCargoList())) {
            List<Long> cargoIds = manifestInfo.getManifestCargoList().stream().map(e -> e.getId()).collect(Collectors.toList());
            Map<Long, List<ManifestCargoCntrDO>> cargoCntrMap = getCargoCntrMap(cargoIds);
            manifestInfo.getManifestCargoList().forEach(e -> {
                List<ManifestCargoCntrDO> cargoCntrList = cargoCntrMap.get(e.getId());
                if (CollectionUtil.isNotEmpty(cargoCntrList)) {
                    e.setCntrNoList(ManifestCargoCntrConvert.INSTANCE.convertList(cargoCntrList));
                }
            });
        }
        return manifestInfo;
    }

    public List<SoForecastDO> seleteSoForecastBySoId(Long soId) {
        LambdaQueryWrapper<SoForecastDO> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(SoForecastDO::getSoId, soId);
        queryWrapper.orderByAsc(SoForecastDO::getId);
        return soForecastMapper.selectList(queryWrapper);
    }

    public Map<Long, List<ManifestCargoCntrDO>> getCargoCntrMap(List<Long> cargoIds) {
        Map<Long, List<ManifestCargoCntrDO>> resultMap = new HashMap<>(16);
        if (CollectionUtil.isEmpty(cargoIds)) {
            return resultMap;
        }
        LambdaQueryWrapperX<ManifestCargoCntrDO> queryWrapperX = new LambdaQueryWrapperX<ManifestCargoCntrDO>();
        queryWrapperX.in(ManifestCargoCntrDO::getCargoId, cargoIds);
        queryWrapperX.orderByDesc(ManifestCargoCntrDO::getId);
        return manifestCargoCntrMapper.selectList(queryWrapperX).stream().collect(Collectors.groupingBy(ManifestCargoCntrDO::getCargoId, LinkedHashMap::new, Collectors.toList()));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @Lock4j(name = "manifestUnique", keys = {"'createSlaveManifest'", "#userId"})
    public Long createSlaveManifest(ManifestSalveReqVO salveReq, String userId, String dataSource) {
        // 这里查询的是主仓单
        ManifestDO manifestInfo = manifestMapper.selectById(salveReq.getId());
        if (null == manifestInfo) {
            throw ServiceExceptionUtil.exception(ManifestErrorCodeConstants.MANIFEST_NOT_EXISTS);
        }
/*        if (!Objects.equals(manifestInfo.getCreator(), userId)) {
            throw ServiceExceptionUtil.exception(ManifestErrorCodeConstants.MANIFEST_USER_LIMIT);
        }*/
        // masterMfNo为空就是主仓单
        String masterMfNo = manifestInfo.getMasterMfNo() != null ? manifestInfo.getMasterMfNo() : manifestInfo.getManifestNo();
        // 状态权限
        ManifestDO masterManifest = findMasterByBookingNo(manifestInfo.getBookingNo());
        if (masterManifest != null && Objects.equals(ConfirmStatusEnum.Y.getValue(), masterManifest.getConfirmStatus())) {
            throw ServiceExceptionUtil.exception(ManifestErrorCodeConstants.MANIFEST_SLAVE_LIMIT);
        }
        //编号校验
        String manifestNoNew = masterMfNo + salveReq.getManifestNoNum();
        ManifestDO oldManifest = findMasterByManifestNo(manifestNoNew);
        if (oldManifest != null) {
            throw ServiceExceptionUtil.exception(ManifestErrorCodeConstants.MANIFEST_SLAVE_EXIST);
        }
        List<ManifestCargoRespVO> cargoList = ManifestCargoConvert.INSTANCE.convertList(findCargoByManifestId(manifestInfo.getId()));
        if (CollectionUtil.isNotEmpty(cargoList)) {
            List<Long> cargoIds = cargoList.stream().map(e -> e.getId()).collect(Collectors.toList());
            Map<Long, List<ManifestCargoCntrDO>> cargoCntrMap = getCargoCntrMap(cargoIds);
            cargoList.forEach(e -> {
                List<ManifestCargoCntrDO> cargoCntrList = cargoCntrMap.get(e.getId());
                if (CollectionUtil.isNotEmpty(cargoCntrList)) {
                    e.setCntrNoList(ManifestCargoCntrConvert.INSTANCE.convertList(cargoCntrList));
                }
            });
        }
        List<ManifestScnDO> scnList = findScnByManifestId(manifestInfo.getId());
        List<ManifestCntrDO> cntrList = findCntrByManifestId(manifestInfo.getId());
        List<ManifestVslvoyDO> vslvoyList = findVslvoyByManifestId(manifestInfo.getId());
        boolean checkDataFull = CollectionUtil.isEmpty(cntrList) || CollectionUtil.isEmpty(scnList) || scnList.size() < 3 || CollectionUtil.isEmpty(cargoList);
        if (checkDataFull || StringUtils.isEmpty(manifestInfo.getPayment())) {
            throw ServiceExceptionUtil.exception(ManifestErrorCodeConstants.MANIFEST_CHECK_MAIN_ERROR);
        }

        manifestInfo.setId(null);
        manifestInfo.setManifestNo(manifestNoNew);
        // 子舱单关联主仓单字段
        manifestInfo.setMasterMfNo(masterMfNo);
        manifestInfo.setConfirmStatus(ConfirmStatusEnum.N.getValue());
        manifestInfo.setDataSource(dataSource);
        reSetNull(manifestInfo);
        manifestMapper.insert(manifestInfo);

        cargoList.forEach(manifestCargo -> {
            manifestCargo.setId(null);
            manifestCargo.setManifestId(manifestInfo.getId());
        });
        List<ManifestCargoDO> newCargoList = ManifestCargoConvert.INSTANCE.convertCreateList(cargoList);
        manifestCargoMapper.insertBatch(newCargoList);
        manifestCargoService.addCargoCntrResp(newCargoList, cargoList);

        scnList.forEach(scn -> {
            scn.setId(null);
            scn.setManifestId(manifestInfo.getId());
            reSetNull(scn);
        });
        manifestScnMapper.insertBatch(scnList);

        cntrList.forEach(cntr -> {
            cntr.setId(null);
            cntr.setManifestId(manifestInfo.getId());
            reSetNull(cntr);
        });
        manifestCntrMapper.insertBatch(cntrList);

        vslvoyList.forEach(vs -> {
            vs.setId(null);
            vs.setManifestId(manifestInfo.getId());
            reSetNull(vs);
        });
        manifestVslvoyMapper.insertBatch(vslvoyList);
        return manifestInfo.getId();
    }


    public ManifestDO findMasterByBookingNo(String bookingNo) {
        LambdaQueryWrapperX<ManifestDO> queryWrapperX = new LambdaQueryWrapperX<ManifestDO>();
        queryWrapperX.eq(ManifestDO::getBookingNo, bookingNo);
        queryWrapperX.isNull(ManifestDO::getMasterMfNo);
        return manifestMapper.selectOne(queryWrapperX);
    }

    @Override
    public List<ManifestDO> findByBookingNos(List<String> bookingNos) {
        LambdaQueryWrapperX<ManifestDO> queryWrapperX = new LambdaQueryWrapperX<ManifestDO>();
        queryWrapperX.in(ManifestDO::getBookingNo, bookingNos);
        return manifestMapper.selectList(queryWrapperX);
    }

    public ManifestDO findMasterByManifestNo(String manifestNo) {
        LambdaQueryWrapperX<ManifestDO> queryWrapperX = new LambdaQueryWrapperX<ManifestDO>();
        queryWrapperX.eq(ManifestDO::getManifestNo, manifestNo);
        return manifestMapper.selectOne(queryWrapperX);
    }

    public List<ManifestCargoDO> findCargoByManifestId(Long manifestId) {
        LambdaQueryWrapperX<ManifestCargoDO> queryWrapperX = new LambdaQueryWrapperX<ManifestCargoDO>();
        queryWrapperX.eq(ManifestCargoDO::getManifestId, manifestId);
        queryWrapperX.orderByAsc(ManifestCargoDO::getId);
        return manifestCargoMapper.selectList(queryWrapperX);
    }

    public List<ManifestScnDO> findScnByManifestId(Long manifestId) {
        LambdaQueryWrapperX<ManifestScnDO> queryWrapperX = new LambdaQueryWrapperX<ManifestScnDO>();
        queryWrapperX.eq(ManifestScnDO::getManifestId, manifestId);
        queryWrapperX.orderByAsc(ManifestScnDO::getId);
        return manifestScnMapper.selectList(queryWrapperX);
    }

    public List<ManifestCntrDO> findCntrByManifestId(Long manifestId) {
        LambdaQueryWrapperX<ManifestCntrDO> queryWrapperX = new LambdaQueryWrapperX<ManifestCntrDO>();
        queryWrapperX.eq(ManifestCntrDO::getManifestId, manifestId);
        queryWrapperX.orderByAsc(ManifestCntrDO::getId);
        return manifestCntrMapper.selectList(queryWrapperX);
    }

    public List<ManifestVslvoyDO> findVslvoyByManifestId(Long manifestId) {
        LambdaQueryWrapperX<ManifestVslvoyDO> queryWrapperX = new LambdaQueryWrapperX<ManifestVslvoyDO>();
        queryWrapperX.eq(ManifestVslvoyDO::getManifestId, manifestId);
        queryWrapperX.orderByAsc(ManifestVslvoyDO::getId);
        return manifestVslvoyMapper.selectList(queryWrapperX);
    }

    public List<ManifestCargoDO> findCargoByManifestId(List<Long> manifestIds) {
        LambdaQueryWrapperX<ManifestCargoDO> queryWrapperX = new LambdaQueryWrapperX<ManifestCargoDO>();
        queryWrapperX.in(ManifestCargoDO::getManifestId, manifestIds);
        queryWrapperX.orderByAsc(ManifestCargoDO::getId);
        return manifestCargoMapper.selectList(queryWrapperX);
    }

    public Map<Long, List<ManifestScnDO>> findScnByManifestId(List<Long> manifestIds) {
        LambdaQueryWrapperX<ManifestScnDO> queryWrapperX = new LambdaQueryWrapperX<ManifestScnDO>();
        queryWrapperX.in(ManifestScnDO::getManifestId, manifestIds);
        queryWrapperX.orderByAsc(ManifestScnDO::getId);
        return manifestScnMapper.selectList(queryWrapperX).stream().collect(Collectors.groupingBy(ManifestScnDO::getManifestId));
    }

    public Map<Long, List<ManifestCntrDO>> findCntrByManifestId(List<Long> manifestIds) {
        LambdaQueryWrapperX<ManifestCntrDO> queryWrapperX = new LambdaQueryWrapperX<ManifestCntrDO>();
        queryWrapperX.in(ManifestCntrDO::getManifestId, manifestIds);
        queryWrapperX.orderByAsc(ManifestCntrDO::getId);
        return manifestCntrMapper.selectList(queryWrapperX).stream().collect(Collectors.groupingBy(ManifestCntrDO::getManifestId));
    }

    public Map<Long, List<ManifestVslvoyDO>> findVslvoyByManifestId(List<Long> manifestIds) {
        LambdaQueryWrapperX<ManifestVslvoyDO> queryWrapperX = new LambdaQueryWrapperX<ManifestVslvoyDO>();
        queryWrapperX.in(ManifestVslvoyDO::getManifestId, manifestIds);
        queryWrapperX.orderByAsc(ManifestVslvoyDO::getId);
        return manifestVslvoyMapper.selectList(queryWrapperX).stream().collect(Collectors.groupingBy(ManifestVslvoyDO::getManifestId));
    }

    @Override
    public List<ManifestDO> getManifestList(Collection<Long> ids) {
        return manifestMapper.selectBatchIds(ids);
    }

    @Override
    public PageResult<com.cmc.cloud.cmclink.doc.vo.manifestvo.manifest.ManifestRespVO> getManifestPage(ManifestPageReqVO reqVO) {
        IPage<ManifestDO> page = Page.of(reqVO.getPageNo(), reqVO.getPageSize());
        IPage<ManifestDO> result = manifestMapper.selectPageList(page, reqVO);
        List<ManifestRespVO> respList = ManifestConvert.INSTANCE.convertList(result.getRecords());
        addSumInfo(respList);
        List<com.cmc.cloud.cmclink.doc.vo.manifestvo.manifest.ManifestRespVO> mainfestList = ManifestConvert.INSTANCE.convertRespList(respList);
        return new PageResult<>(mainfestList, result.getTotal());
    }

    public void addSumInfo(List<ManifestRespVO> respList) {
        if (CollectionUtil.isNotEmpty(respList)) {
            // 创建分单如果POL是大连CNDLC 并且是一箱多票（主单只有一个箱子 、建多个分单）分单号允许输入*号
            List<String> bookingNoAsterisk = respList.stream().filter(e -> Objects.equals("CNDLC", e.getPolCode())).map(e -> e.getBookingNo()).distinct().collect(Collectors.toList());
            Map<String, List<ManifestCntrSumCheckVO>> masterSumAllMap = new HashMap<>(16);
            if (CollectionUtil.isNotEmpty(bookingNoAsterisk)) {
                masterSumAllMap = manifestCntrMapper.selectMasterSumByBookingNo(bookingNoAsterisk).stream().collect(Collectors.groupingBy(ManifestCntrSumCheckVO::getBookingNo));
            }

            List<Long> manifestIds = respList.stream().map(e -> e.getId()).collect(Collectors.toList());
            List<String> bookingNos = respList.stream().map(e -> e.getBookingNo()).distinct().collect(Collectors.toList());
            Map<Long, ManifestCntrSumVO> cntrMap = manifestCntrMapper.selectSumByManifestIds(manifestIds).stream().collect(Collectors.toMap(ManifestCntrSumVO::getManifestId, e -> e));
            Map<Long, ManifestCargoSumVO> cargoMap = manifestCargoMapper.selectSumByManifestIds(manifestIds).stream().collect(Collectors.toMap(ManifestCargoSumVO::getManifestId, e -> e));
            Map<String, SoForecastCntrSumVO> forecastCntrMap = soForecastMapper.selectSumDescByBookingNo(bookingNos).stream().collect(Collectors.toMap(SoForecastCntrSumVO::getBookingNo, e -> e));

            for (ManifestRespVO e : respList) {
                ManifestCntrSumVO cntrSum = cntrMap.get(e.getId());
                ManifestCargoSumVO cargoSum = cargoMap.get(e.getId());
                SoForecastCntrSumVO forecastSum = forecastCntrMap.get(e.getBookingNo());
                List<ManifestCntrSumCheckVO> masterCntr = masterSumAllMap.get(e.getBookingNo());
                if (CollectionUtil.isNotEmpty(masterCntr) && masterCntr.size() == 1) {
                    e.setUseAsterisk(true);
                }
                if (cntrSum != null) {
                    e.setCntrDesc(cntrSum.getCntrDesc());
                }
                if (cargoSum != null) {
                    e.setCargoTypeSum(cargoSum.getCargoTypeSum());
                    e.setGrossWeightSum(cargoSum.getGrossWeightSum());
                }
                if (forecastSum != null) {
                    e.setForecastCntrDesc(forecastSum.getForecastCntrDesc());
                }
            }
        }
    }


    public boolean checkManifestLcl(ManifestCntrCheckLclReqVO reqVO) {
        ManifestDO manifest = manifestMapper.selectById(reqVO.getManifestId());
        if (manifest == null) {
            throw ServiceExceptionUtil.exception(ManifestErrorCodeConstants.MANIFEST_NOT_EXISTS);
        }
        ManifestCntrLclReqVO cntrLclReq = new ManifestCntrLclReqVO();
        cntrLclReq.setCntrNoList(Arrays.asList(reqVO.getCntrNo()));
        cntrLclReq.setBookingVesselList(Arrays.asList(manifest.getBookingVessel()));
        cntrLclReq.setBookingVoyageList(Arrays.asList(manifest.getBookingVoyage()));
        List<ManifestCntrLclRespVO> cntrList = manifestMapper.selectManifestLcl(cntrLclReq);
        Map<String, List<ManifestCntrLclRespVO>> cntrMap = cntrList.stream().collect(Collectors.groupingBy(ManifestCntrLclRespVO::getCntrNo));
        List<ManifestCntrLclRespVO> matchCntrList = cntrMap.get(reqVO.getCntrNo());
        return checkLcl(matchCntrList, manifest);
    }

    public Map<String, Boolean> checkBatchManifestLcl(Long manifestId, List<String> cntrNos) {
        Map<String, Boolean> rtnMap = new HashMap<>(16);
        ManifestDO manifest = manifestMapper.selectById(manifestId);
        if (manifest == null) {
            throw ServiceExceptionUtil.exception(ManifestErrorCodeConstants.MANIFEST_NOT_EXISTS);
        }
        ManifestCntrLclReqVO cntrLclReq = new ManifestCntrLclReqVO();
        cntrLclReq.setCntrNoList(cntrNos);
        cntrLclReq.setBookingVesselList(Arrays.asList(manifest.getBookingVessel()));
        cntrLclReq.setBookingVoyageList(Arrays.asList(manifest.getBookingVoyage()));
        List<ManifestCntrLclRespVO> cntrList = manifestMapper.selectManifestLcl(cntrLclReq);
        Map<String, List<ManifestCntrLclRespVO>> cntrMap = cntrList.stream().collect(Collectors.groupingBy(ManifestCntrLclRespVO::getCntrNo));
        cntrMap.keySet().forEach(key -> {
            List<ManifestCntrLclRespVO> matchCntrList = cntrMap.get(key);
            rtnMap.put(key, checkLcl(matchCntrList, manifest));
        });
        return rtnMap;
    }

    public boolean checkLcl(List<ManifestCntrLclRespVO> matchCntrList, ManifestDO manifest) {
        ManifestCntrLclRespVO master = null;
        int otherCount = 0;
        int childCount = 0;
        if (CollectionUtil.isNotEmpty(matchCntrList)) {
            for (ManifestCntrLclRespVO cntr : matchCntrList) {
                if (Objects.equals(manifest.getManifestNo(), cntr.getManifestNo())) {
                    continue;
                }
                // 当前的是子舱单 判断主舱单
                if (StringUtils.isNotEmpty(manifest.getMasterMfNo())) {
                    if (Objects.equals(manifest.getMasterMfNo(), cntr.getManifestNo())) {
                        master = cntr;
                        continue;
                    }
                } else {
                    // 判断当前匹配的是主
                    if (Objects.equals(manifest.getManifestNo(), cntr.getMasterMfNo())) {
                        childCount++;
                        continue;
                    }
                }
                otherCount++;
            }
        }
        if (otherCount > 0 || childCount > 1) {
            return true;
        }
        //返回主舱单中的状态
        if (master != null && master.getLcl() != null) {
            return master.getLcl();
        }
        return false;
    }

    @Override
    public String checkConfirmLcl(List<Long> ids, String userId) {
        List<ManifestDO> list = checkByUserId(ids, ConfirmStatusEnum.N.getValue(), userId);
        List<String> bookingNoList = list.stream().map(e -> e.getBookingNo()).distinct().collect(Collectors.toList());
        // 查询所有主子舱单
        List<ManifestDO> allManifestList = findByBookingNoList(bookingNoList, userId);
        Map<Long, ManifestDO> allManifestMap = allManifestList.stream().collect(Collectors.toMap(ManifestDO::getId, e -> e));
        // 查看所有箱
        LambdaQueryWrapperX<ManifestCntrDO> queryWrapperX = new LambdaQueryWrapperX<ManifestCntrDO>();
        queryWrapperX.in(ManifestCntrDO::getManifestId, allManifestList.stream().map(e -> e.getId()).collect(Collectors.toSet()));
        List<ManifestCntrDO> allManifestCntrList = manifestCntrMapper.selectList(queryWrapperX);
        // 查询航线相关的 所有相关箱
        ManifestCntrLclReqVO cntrLclReq = new ManifestCntrLclReqVO();
        cntrLclReq.setCntrNoList(allManifestCntrList.stream().map(e -> e.getCntrNo()).collect(Collectors.toList()));
        cntrLclReq.setBookingVesselList(allManifestList.stream().map(e -> e.getBookingVessel()).collect(Collectors.toList()));
        cntrLclReq.setBookingVoyageList(allManifestList.stream().map(e -> e.getBookingVoyage()).collect(Collectors.toList()));
        List<ManifestCntrLclRespVO> cntrList = manifestMapper.selectManifestLcl(cntrLclReq);
        Map<String, List<ManifestCntrLclRespVO>> cntrMap = cntrList.stream().collect(Collectors.groupingBy(e -> e.getBookingVessel() + "_" + e.getBookingVoyage() + "_" + e.getCntrNo()));
        List<String> errorList = new ArrayList<>(16);
        Map<String, List<String>> errorMap = new HashMap<>(16);
        allManifestCntrList.forEach(e -> {
            ManifestDO manifest = allManifestMap.get(e.getManifestId());
            String keylcl = manifest.getBookingVessel() + "_" + manifest.getBookingVoyage() + "_" + e.getCntrNo();
            List<ManifestCntrLclRespVO> matchCntrList = cntrMap.get(keylcl);
            boolean check = checkLcl(matchCntrList, manifest);
            //检查拼箱状态是否正确
            if (Objects.equals(e.getLcl(), check) == false) {
                if (check) {
                    // 实际存在拼箱
                    errorMap.put(e.getCntrNo(), matchCntrList.stream().map(t -> t.getManifestNo()).sorted().collect(Collectors.toList()));
                } else {
                    errorList.add(manifest.getManifestNo() + "中" + e.getCntrNo() + "不存在拼箱");
                }
            }
        });
        if (CollectionUtil.isNotEmpty(errorMap)) {
            for (Map.Entry<String, List<String>> t : errorMap.entrySet()) {
                errorList.add(t.getKey() + "在" + StringUtils.join(t.getValue(), ",") + "存在拼箱");
            }
        }
        if (CollectionUtil.isNotEmpty(errorList)) {
            return ManifestErrorCodeConstants.MANIFEST_CHECK_LCL_ERROR.getMsg() + StringUtils.join(errorList, ";");
        }
        return null;
    }

    @Transactional(rollbackFor = Exception.class)
    public CommonResult<String> manifestEdiSync(ManifestEdiReqVO ediInfo, String userId) {
        //9＝原始报文 4＝更改报文 3＝删除报文
        ManifestCreateInfoReqVO createInfo = ediInfo.getData();
        // 查找主舱单中箱信息
        Set<String> manifestCntrAllSet = null;
        // 检查箱和关系
        List<String> notInMainList = new ArrayList<>(16);
        List<String> notInCntrList = new ArrayList<>(16);
        List<String> errorCntrList = new ArrayList<>(16);
        if (StringUtils.isNotEmpty(createInfo.getManifest().getMasterMfNo())) {
            ManifestDO masterManifest = findMasterByBookingNo(createInfo.getManifest().getBookingNo());
            if (masterManifest != null) {
                manifestCntrAllSet = ManifestCntrConvert.INSTANCE.convertList(findCntrByManifestId(masterManifest.getId())).stream().map(e -> e.getCntrNo()).collect(Collectors.toSet());
            } else {
                return CommonResult.error(new ErrorCode(500, createInfo.getManifest().getMasterMfNo() + "主舱单未创建"));
            }
        }
        if (ManifestEdiReqVO.UPDATE_OPERATION.equals(ediInfo.getFuncCode()) || ManifestEdiReqVO.CREATE_OPERATION.equals(ediInfo.getFuncCode())) {
            List<String> cntrList = new ArrayList<>(16);
            if (CollectionUtil.isNotEmpty(createInfo.getManifestCntrList())) {
                for (ManifestCntrUpdateReqVO e : createInfo.getManifestCntrList()) {
                    if (manifestCntrAllSet != null && manifestCntrAllSet.contains(e.getCntrNo()) == false) {
                        notInMainList.add(e.getCntrNo());
                    }
                    if (CheckUtil.isValidCntrNo(e.getCntrNo()) == false) {
                        //errorCntrList.add(e.getCntrNo());
                    }
                    cntrList.add(e.getCntrNo());
                }
            }

            if (CollectionUtil.isNotEmpty(createInfo.getManifestCargoList())) {
                for (ManifestCargoUpdateReqVO cargo : createInfo.getManifestCargoList()) {
                    if (CollectionUtil.isNotEmpty(cargo.getCntrNoList())) {
                        for (ManifestCargoCntrUpdateReqVO r : cargo.getCntrNoList()) {
                            if (cntrList.contains(r.getCntrNo()) == false) {
                                notInCntrList.add(r.getCntrNo());
                            }
                        }
                    }
                }
            }

            if (CollectionUtil.isNotEmpty(notInMainList) || CollectionUtil.isNotEmpty(notInCntrList)) {
                if (CollectionUtil.isNotEmpty(errorCntrList)) {
                    return CommonResult.error(new ErrorCode(500, StringUtils.join(errorCntrList, ",") + "箱号规则不匹配"));
                }
                if (CollectionUtil.isNotEmpty(notInMainList)) {
                    return CommonResult.error(new ErrorCode(500, StringUtils.join(notInMainList, ",") + "在主舱单中不存在"));
                }
                if (CollectionUtil.isNotEmpty(notInCntrList)) {
                    return CommonResult.error(new ErrorCode(500, StringUtils.join(notInCntrList, ",") + "货物关联的箱在箱信息中不存在"));
                }
            }
        }
        if (ManifestEdiReqVO.CREATE_OPERATION.equals(ediInfo.getFuncCode())) {
            ManifestDO oldManifestDO = findByManifestNo(createInfo.getManifest().getManifestNo());
            if (oldManifestDO != null) {
                return CommonResult.error(new ErrorCode(500, createInfo.getManifest().getMasterMfNo() + "已经存在,不能重复创建"));
            }
            ManifestDO newManifest = ManifestConvert.INSTANCE.convert(createInfo.getManifest());
            manifestMapper.insert(newManifest);
            saveEdiData(createInfo, newManifest.getId());
            return CommonResult.success(createInfo.getManifest().getManifestNo() + "新建成功");
        }
        ManifestDO oldManifestDO = findByManifestNo(createInfo.getManifest().getManifestNo());
        if (oldManifestDO == null) {
            return CommonResult.error(new ErrorCode(500, createInfo.getManifest().getManifestNo() + "不存在,不能修改或删除"));
        }

        if (ManifestEdiReqVO.UPDATE_OPERATION.equals(ediInfo.getFuncCode())) {
            ManifestDO updateManifest = ManifestConvert.INSTANCE.convert(createInfo.getManifest());
            updateManifest.setId(oldManifestDO.getId());
            manifestMapper.updateById(updateManifest);
            saveEdiData(createInfo, oldManifestDO.getId());
            return CommonResult.success(createInfo.getManifest().getManifestNo() + "修改成功");
        }
        if (ManifestEdiReqVO.DELETE_OPERATION.equals(ediInfo.getFuncCode())) {
            List<Long> ids = Arrays.asList(oldManifestDO.getId());
            manifestMapper.deleteBatchIds(ids);
            deleteCargaoByManifestIds(ids);
            deleteScnByManifestIds(ids);
            deleteCntrByManifestIds(ids);
            deleteVslvoyByManifestIds(ids);
            return CommonResult.success(createInfo.getManifest().getManifestNo() + "删除成功");
        }
        return CommonResult.success("成功");
    }

    public void saveEdiData(ManifestCreateInfoReqVO createInfo, Long manifestId) {
        manifestCargoService.createOrUpdateAndDeleteBatch(manifestId, createInfo.getManifestCargoList(), null);
        manifestCntrService.createOrUpdateAndDeleteBatch(manifestId, createInfo.getManifestCntrList(), null);
        manifestScnService.createOrUpdateAndDeleteBatch(manifestId, createInfo.getManifestScnList(), null);
        manifestVslvoyService.createOrUpdateAndDeleteBatch(manifestId, createInfo.getManifestVslvoyList(), null);
    }

    public void deleteVslvoyByManifestIds(List<Long> manifestIds) {
        LambdaUpdateWrapper<ManifestVslvoyDO> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.in(ManifestVslvoyDO::getManifestId, manifestIds);
        manifestVslvoyMapper.delete(updateWrapper);
    }

    public ManifestDO findByManifestNo(String manifestNo) {
        LambdaQueryWrapperX<ManifestDO> queryWrapperX = new LambdaQueryWrapperX<ManifestDO>();
        queryWrapperX.eq(ManifestDO::getManifestNo, manifestNo);
        return manifestMapper.selectOne(queryWrapperX);
    }

    @Override
    public CommonResult<List<ManifestInfoEdiRespVO>> manifestEdiDetailQuery(ManifestEdiQueryReqVO query) {
        List<String> errorList = checkEdiParams(query, true);
        //参数校验
        if (CollectionUtil.isNotEmpty(errorList)) {
            return CommonResult.error(new ErrorCode(500, StringUtils.join(errorList, ",")));
        }
        List<ManifestDO> manifestList = queryForEdi(query);
        if (CollectionUtil.isEmpty(manifestList)) {
            return CommonResult.success();
        }
        List<Long> manifestIds = manifestList.stream().map(e -> e.getId()).collect(Collectors.toList());
        List<ManifestCargoDO> cargoList = findCargoByManifestId(manifestIds);
        List<Long> cargoIds = cargoList.stream().map(e -> e.getId()).collect(Collectors.toList());
        Map<Long, List<ManifestCargoCntrDO>> cargoCntrMap = getCargoCntrMap(cargoIds);
        Map<Long, List<ManifestCargoDO>> cargoMap = cargoList.stream().collect(Collectors.groupingBy(ManifestCargoDO::getManifestId));
        Map<Long, List<ManifestScnDO>> scnMap = findScnByManifestId(manifestIds);
        Map<Long, List<ManifestCntrDO>> cntrMap = findCntrByManifestId(manifestIds);
        Map<Long, List<ManifestVslvoyDO>> vslvoyMap = findVslvoyByManifestId(manifestIds);

        List<ManifestInfoEdiRespVO> manifestInfoList = new ArrayList(16);

        for (ManifestDO manifest : manifestList) {
            ManifestInfoEdiRespVO manifestInfo = new ManifestInfoEdiRespVO();
            manifestInfo.setManifest(ManifestConvert.INSTANCE.convert(manifest));
            manifestInfo.setManifestCargoList(ManifestCargoConvert.INSTANCE.convertList(cargoMap.get(manifest.getId())));
            manifestInfo.setManifestScnList(ManifestScnConvert.INSTANCE.convertList(scnMap.get(manifest.getId())));
            manifestInfo.setManifestCntrList(ManifestCntrConvert.INSTANCE.convertList(cntrMap.get(manifest.getId())));
            manifestInfo.setManifestVslvoyList(ManifestVslvoyConvert.INSTANCE.convertList(vslvoyMap.get(manifest.getId())));
            manifestInfo.getManifestCargoList().forEach(e -> {
                List<ManifestCargoCntrDO> cargoCntrList = cargoCntrMap.get(e.getId());
                if (CollectionUtil.isNotEmpty(cargoCntrList)) {
                    e.setCntrNoList(ManifestCargoCntrConvert.INSTANCE.convertList(cargoCntrList));
                }
            });
            manifestInfoList.add(manifestInfo);
        }
        checkAssociate(manifestInfoList);
        return CommonResult.success(manifestInfoList);
    }

    public void checkAssociate(List<ManifestInfoEdiRespVO> manifestInfoList) {
        List<String> bookingVesselList = new ArrayList<>(16);
        List<String> bookingVoyageList = new ArrayList<>(16);
        for (ManifestInfoEdiRespVO info : manifestInfoList) {
            bookingVesselList.add(info.getManifest().getBookingVessel());
            bookingVoyageList.add(info.getManifest().getBookingVoyage());
        }
        // 检查关联关系
        ManifestCntrLclReqVO cntrLclReq = new ManifestCntrLclReqVO();
        cntrLclReq.setBookingVesselList(bookingVesselList);
        cntrLclReq.setBookingVoyageList(bookingVoyageList);
        List<ManifestCntrLclRespVO> cntrAllList = manifestMapper.selectManifestLcl(cntrLclReq);
        Map<String, List<ManifestCntrLclRespVO>> cntrMap = new HashMap<>(16);
        Map<String, List<ManifestCntrLclRespVO>> manifestCntrMap = new HashMap<>(16);
        Map<String, Set<String>> associatedChildMap = new HashMap<>(16);
        for (ManifestCntrLclRespVO e : cntrAllList) {
            String key = e.getBookingVessel() + "_" + e.getBookingVoyage() + "_" + e.getCntrNo();
            if (cntrMap.get(key) == null) {
                List<ManifestCntrLclRespVO> list = new ArrayList<>(16);
                list.add(e);
                cntrMap.put(key, list);
            } else {
                cntrMap.get(key).add(e);
            }
            if (manifestCntrMap.get(e.getManifestNo()) == null) {
                List<ManifestCntrLclRespVO> list = new ArrayList<>(16);
                list.add(e);
                manifestCntrMap.put(e.getManifestNo(), list);
            } else {
                manifestCntrMap.get(e.getManifestNo()).add(e);
            }
            if (associatedChildMap.get(e.getMasterMfNo()) == null) {
                Set<String> setChild = new HashSet<>(16);
                setChild.add(e.getManifestNo());
                associatedChildMap.put(e.getMasterMfNo(), setChild);
            } else {
                associatedChildMap.get(e.getMasterMfNo()).add(e.getManifestNo());
            }

        }

        for (ManifestInfoEdiRespVO info : manifestInfoList) {
            List<String> associatedCntrManifestList = new ArrayList<>(16);
            if (CollectionUtil.isEmpty(info.getManifestCntrList())) {
                continue;
            }
            for (ManifestCntrRespVO cntr : info.getManifestCntrList()) {
                String lclKey = info.getManifest().getBookingVessel() + "_" + info.getManifest().getBookingVoyage() + "_" + cntr.getCntrNo();
                associatedCntrManifestList.addAll(checkLcl(cntrMap.get(lclKey), info.getManifest()));
            }
            Set<String> childList = associatedChildMap.get(info.getManifest().getManifestNo());
            if (CollectionUtil.isNotEmpty(childList)) {
                info.setAssociatedNoList(new ArrayList<>(16));
                for (String child : childList) {
                    //默认是分单
                    info.getAssociatedNoList().add(new ManifestInfoEdiRespVO.ManifestAssociate(child, "SPLIT"));
                }
            }
            if (CollectionUtil.isNotEmpty(associatedCntrManifestList)) {
                associatedCntrManifestList = associatedCntrManifestList.stream().distinct().collect(Collectors.toList());
                info.setAssociatedCntrManifestList(associatedCntrManifestList);
                List<ManifestCntrEdiBaseVO> associatedCntrList = new ArrayList<>(16);
                for (String manifestNo : associatedCntrManifestList) {
                    List<ManifestCntrLclRespVO> otherCntr = manifestCntrMap.get(manifestNo);
                    if (CollectionUtil.isNotEmpty(otherCntr)) {
                        associatedCntrList.addAll(ManifestCntrConvert.INSTANCE.convertEdiList(otherCntr));
                    }
                }
                Set<String> cntrSet = new HashSet<>(16);
                // 数据去重
                List<ManifestCntrEdiBaseVO> associatedCntrNewList = new ArrayList<>(16);
                associatedCntrList.forEach(e -> {
                    if (cntrSet.contains(e.getCntrNo()) == false) {
                        associatedCntrNewList.add(e);
                    } else {
                        cntrSet.add(e.getCntrNo());
                    }
                });
                info.setAssociatedCntrList(associatedCntrNewList);
            }
        }
    }

    public List<String> checkLcl(List<ManifestCntrLclRespVO> matchCntrList, ManifestRespVO manifest) {
        List<String> sameManifest = new ArrayList<>();
        if (CollectionUtil.isNotEmpty(matchCntrList)) {
            for (ManifestCntrLclRespVO cntr : matchCntrList) {
                if (Objects.equals(manifest.getManifestNo(), cntr.getManifestNo())) {
                    continue;
                }
                // 当前的是子舱单 判断主舱单
                if (StringUtils.isNotEmpty(manifest.getMasterMfNo())) {
                    if (Objects.equals(manifest.getMasterMfNo(), cntr.getManifestNo())) {
                        continue;
                    }
                } else {
                    // 判断当前匹配的是主
                    if (Objects.equals(manifest.getManifestNo(), cntr.getMasterMfNo())) {
                        continue;
                    }
                }
                //其他拼箱
                sameManifest.add(cntr.getManifestNo());
            }
        }
        return sameManifest;
    }

    @Override
    public CommonResult<List<ManifestRespVO>> manifestEdiQuery(ManifestEdiQueryReqVO query) {
        List<String> errorList = checkEdiParams(query, false);
        //参数校验
        if (CollectionUtil.isNotEmpty(errorList)) {
            return CommonResult.error(new ErrorCode(500, StringUtils.join(errorList, ",")));
        }
        return CommonResult.success(ManifestConvert.INSTANCE.convertList(queryForEdi(query)));
    }

    public List<ManifestDO> queryForEdi(ManifestEdiQueryReqVO query) {
        List<Long> ids = manifestMapper.selectEdiList(query);
        if (CollectionUtil.isNotEmpty(ids)) {
            return manifestMapper.selectBatchIds(ids);
        }
        return Collections.emptyList();
    }

    public List<String> checkEdiParams(ManifestEdiQueryReqVO query, boolean needManifestNoList) {
        List<String> errorList = new ArrayList<>(16);
        if (StringUtils.isEmpty(query.getVesselCode())) {
            errorList.add("船舶代码不能为空");
        }
        if (needManifestNoList && CollectionUtil.isEmpty(query.getManifestNoList())) {
            errorList.add("舱单号列表不能为空");
        }
        boolean check = StringUtils.isEmpty(query.getVoyageIm()) && StringUtils.isEmpty(query.getVoyageEx()) && StringUtils.isEmpty(query.getPolCode()) && StringUtils.isEmpty(query.getPodCode());
        if (check) {
            errorList.add("航次和港口代码不能全为空");
        }
        return errorList;
    }


    @Override
    public List<ManifestCntrRespVO> getManifestCntrListByBookingNo(String bookingNo) {
        ManifestDO master = findMasterByBookingNo(bookingNo);
        if (master != null) {
            List<ManifestCntrRespVO> list = ManifestCntrConvert.INSTANCE.convertList(findCntrByManifestId(master.getId()));
            if (!CollectionUtil.isEmpty(list)) {
                return list;
            }
        }
        return Collections.emptyList();
    }
}
